API authentication (snap/3.0/CLI)

2.7 2.8 2.9 3.0
Snap CLI ~ UI CLI ~ UI CLI ~ UI CLI ~ UI
Packages CLI ~ UI CLI ~ UI CLI ~ UI CLI ~ UI

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

#!/usr/bin/env python3
from argparse import ArgumentParser

import requests
from oauthlib import oauth1


def make_maas_headers(url, key=None):
    if key:
        client_key, resource_owner_key, resource_owner_secret = key.split(
            ":", 3
        )
        client = oauth1.Client(
            client_key=client_key,
            resource_owner_key=resource_owner_key,
            resource_owner_secret=resource_owner_secret,
            signature_method=oauth1.SIGNATURE_PLAINTEXT,
        )
        _, headers, _ = client.sign(url)
    else:
        headers = {}
    # Work around for LP:1929643
    headers["Accept"] = "application/json"
    return headers


def main():
    parser = ArgumentParser(description="Simple MAAS OAUTH1 client")
    parser.add_argument(
        "-k",
        "--key",
        help="The OAUTH1 key to use when authentication is needed.",
    )
    parser.add_argument(
        "-m",
        "--method",
        # Mapping from the CLI
        # read - get
        # update - put
        # post - most actions
        # delete - delete
        choices=("get", "put", "post", "delete"),
        default="get",
        help="The HTTP method to use for the request.",
    )
    parser.add_argument(
        "-o", "--operation", help="The operation to call on the endpoint."
    )
    parser.add_argument("url", help="The MAAS URL to access.")
    parser.add_argument(
        "arguments", nargs="*", help="Arguments to send to the call."
    )

    args = parser.parse_args()

    if args.operation:
        # Operations are sometimes shown in documentation with a dash but the
        # CLI automatically converts them to an underscore.
        params = {"op": args.operation.replace("-", "_")}
    else:
        params = {}

    arguments = {}
    for arg in args.arguments:
        key, value = arg.split("=", 2)
        arguments[key] = value

    if args.method in ["post", "put"]:
        # Arguments are sent as form data in post and put as per RFC7231.
        data = arguments
    else:
        data = {}
        params.update(**arguments)

    method = getattr(requests, args.method)
    headers = make_maas_headers(args.url, args.key)
    response = method(args.url, headers=headers, params=params, data=data)

    if not response.ok:
        print(f"ERROR: status code - {response.status_code}\n\n")

    print(response.content.decode())


if __name__ == "__main__":
    main()

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>")