API authentication

MAAS’s API uses OAuth as its authentication mechanism. This isn’t third-party (3-legged) OAuth, so the process used is what’s commonly referred to as 0-legged OAuth: the consumer accesses protected resources by submitting OAuth signed requests.

Note that some API endpoints support unauthenticated requests (i.e. anonymous access). See the API documentation for details.

Two questions you may have:

  1. How can I perform authenticated requests in python?
  2. How can I perform authenticated requests in ruby?

Here are two examples on how to perform an authenticated GET request to retrieve the list of nodes. The <key>, <secret>, <consumer_key> tokens are the three elements that compose the API key (API key = ‘<consumer_key>:<key>:<secret>’).

Python

import oauth.oauth as oauth
import httplib2
import uuid

def perform_API_request(site, uri, method, key, secret, consumer_key):
    resource_tok_string = "oauth_token_secret=%s&oauth_token=%s" % (
        secret, key)
    resource_token = oauth.OAuthToken.from_string(resource_tok_string)
    consumer_token = oauth.OAuthConsumer(consumer_key, "")

    oauth_request = oauth.OAuthRequest.from_consumer_and_token(
        consumer_token, token=resource_token, http_url=site,
        parameters={'oauth_nonce': uuid.uuid4().hex})
    oauth_request.sign_request(
        oauth.OAuthSignatureMethod_PLAINTEXT(), consumer_token,
        resource_token)
    headers = oauth_request.to_header()
    url = "%s%s" % (site, uri)
    http = httplib2.Http()
    return http.request(url, method, body=None, headers=headers)

# API key = '<consumer_key>:<key>:<secret>'
response = perform_API_request(
    'http://server/MAAS/api/1.0', '/nodes/?op=list', 'GET', '<key>', '<secret>',
    '<consumer_key>')

Ruby

require 'oauth'
require 'oauth/signature/plaintext'

def perform_API_request(site, uri, key, secret, consumer_key)
    consumer = OAuth::Consumer.new(
        consumer_key, "",
        { :site => "http://localhost/MAAS/api/1.0",
          :scheme => :header, :signature_method => "PLAINTEXT"})
    access_token = OAuth::AccessToken.new(consumer, key, secret)
    return access_token.request(:get, "/nodes/?op=list")
end

# API key = "<consumer_key>:<key>:<secret>"
response = perform_API_request(
     "http://server/MAAS/api/1.0", "/nodes/?op=list", "<key>", "<secret>",
     "consumer_key>")



The codeblock provided while I’m no python expert, I couldn’t resolve those libs easily. I found this to run without extra work from a maas deployment of 20.04 without anything extra other than requiring requests_oauthlib. I think this could be re-written to only use oauthlib which if I recall is built in. Either way, the code sample there helped me arrive here, but I find this to be more readable to understanding the oauth flow, and how it might relate to other oAuth flows.

    import argparse
    from sys import argv
    import requests
    from requests_oauthlib import OAuth1

    def oauth_request(site, uri, key, secret, consumer_key):
        headeroauth = OAuth1(key,
            client_secret='',
            resource_owner_key=secret,
            resource_owner_secret=consumer_key,
            signature_method='PLAINTEXT')
        r = requests.get(f'{site}{uri}', auth=headeroauth)
        print(r.text)
        return r

    if __name__ == '__main__':
        parser = argparse.ArgumentParser()
        parser.add_argument('--server', help = "Maas server.  ex. http://maas:5240/MAAS ")
        parser.add_argument('--key', help = "Maas key.  ex. ABCD:abcd:abcd ")
        parser.add_argument('--secret', help = "Maas secret.  ex. abcd:ABCD:abcd ")
        parser.add_argument('--consumer', help = "Maas consumer key.  ex. abcd:abcd:ABCD ")
        args = parser.parse_args(argv[1:])
        # API key = '<consumer_key>:<key>:<secret>'
        response = oauth_request(
            f'{args.server}/api/2.0', '/pods/',
            args.key, args.secret, args.consumer)
        print(response

I tried the code with a valid API key and it returned “Invalid API key”.

I removed the parser portion but am running into a problem.

def oauth_request(site, uri, key, secret, consumer_key):
headeroauth = OAuth1(key,
client_secret=’’,
resource_owner_key=secret,
resource_owner_secret=consumer_key,
signature_method=‘PLAINTEXT’)
r = requests.get(f’{site}{uri}’, auth=headeroauth)
print(r.text)
return r

#API key = ‘<consumer_key>::’
consumer_key, key, secret = MAASAPIKEY.split(’:’)
response = oauth_request( f’{MAASURL}/api/2.0’, ‘/machines/’, key, secret, consumer_key)
print(response)

Any ideas why I’d get the error?

The links from the “Two questions you may have” part are wrong. Currently they are:

Two questions you may have:

  1. How can I perform authenticated requests in python?
  2. How can I perform authenticated requests in ruby?

The following would link to the two sections that are included on the page itself:

Two questions you may have:

  1. How can I perform authenticated requests in python?
  2. How can I perform authenticated requests in ruby?
1 Like

thanks, @tomkivlin. nice catch, we’ll fix.

The oauth dependency in the Python example appears to be Python2 only. When trying the example in Python3 I get the following error:

 File ".venv/lib/python3.8/site-packages/oauth/oauth.py", line 29, in <module>
    import urlparse
ModuleNotFoundError: No module named 'urlparse'

urlparse was moved to urllib.parse with Python3; I don’t have a working solution for Python 3 yet.

Perhaps in the interim it might be a good idea to highlight the example as Python 2 only?