authenticator

package
v0.0.0-...-70b770e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 11, 2022 License: Apache-2.0 Imports: 12 Imported by: 0

README

Opstrace tenant API authenticator

Public key serialization format

Public keys need to be injected in the PEM-encoded X.509 SubjectPublicKeyInfo format, which is what OpenSSL uses when writing a public key to a "PEM file".

See this StackOverflow answer for a lovely discussion about the X.509 SubjectPublicKeyInfo PEM serialization format, and how the difference between the header BEGIN PUBLIC KEY (supported here) and BEGIN RSA PUBLIC KEY (not supported here) matters a lot.

Example flow for generating an RSA keypair using OpenSSL, and for subsequently writing the public key out to a PEM file containing the format expected here:

$ openssl genrsa -out keypair.pem 2048
$ openssl rsa -in keypair.pem -out public.pem -pubout
$ cat public.pem
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1dJBQDgTL8ltms5ksNrW
...
JuMRuClKi4dAFJVtW64A/Z86cYZ92CtmEP3rVkX7oouMUy5bYwbRHcNtKf4JD2KR
kQIDAQAB
-----END PUBLIC KEY-----

Key ID calculation

For raw RSA public keys, there is no canonical way to build a key id. Here, we define the following procedure:

  • Take PEM text : -----BEGIN PUBLIC KEY-<...>-END PUBLIC KEY-----
  • Strip leading and trailing whitespace (in case it sneaked in).
  • Use byte representation of PEM text (use utf-8/ascii).
  • Build SHA1 hash from these bytes, and represent the resulting hash as a string in hex notation.

Python program example:

import hashlib
import sys

keytext = ''.join(l for l in sys.stdin)
data = keytext.strip().encode('utf-8')
print(hashlib.sha1(data).hexdigest())

In this case saved as keyid.py. Usage:

$ cat public.pem | python keyid.py
d6de1ae63a549c56307b0b0b20c39dcf921b4a8a

Key set config: JSON structure spec

A flat map (object), with keys and values being strings.

Each key-value pair is expected to represent an RSA public key.

Each JSON key is expected to be the key ID corresponding to the RSA pub key (see above for key ID derivation method specification).

Each value is expected to be a JSON string, describing the pub key in the PEM-encoded X.509 SubjectPublicKeyInfo format (JSON string with escaped newlines).

Example Python program to generate such a JSON doc for multiple keys:

import hashlib
import sys
import json

infiles = sys.argv[1:]

stripped_pem_strings = []
for fp in infiles:
    with open(fp, 'rb') as f:
        stripped_pem_strings.append(f.read().decode('utf-8').strip())

keymap = {}
for sps in stripped_pem_strings:
    data = sps.encode('utf-8')
    kid = hashlib.sha1(data).hexdigest()
    keymap[kid] = sps

outjson = json.dumps(keymap, indent=2)
print(outjson)

In this case saved as build-key-config-json.py. Usage:

$ python build-key-config-json.py public.pem public2.pem
{
  "d6de1ae63a549c56307b0b0b20c39dcf921b4a8a": "-----BEGIN PUBLIC KEY-----\nM[...]B\n-----END PUBLIC KEY-----",
  "44610d7c2277d33a68abae86315eb6ea9b3734a9": "-----BEGIN PUBLIC KEY-----\nM[...]B\n-----END PUBLIC KEY-----"
}

Note: when injecting this JSON doc via environment through a docker run layer then keep the JSON doc on a single line (no literal newline char). In the Python program above, this means removing indent=2. You can always pretty-print that JSON with | jq.

Documentation

Index

Constants

View Source
const TenantAPITokenForKey624 = "" /* 605-byte string literal not displayed */

Tenant API token created with

./build/bin/opstrace ta-create-token instancename tenantfoo keypair.pem

Expires Oct 02, 2031

View Source
const TestKeysetEnvValThreePubkeys = `` /* 1524-byte string literal not displayed */

Note that the private key corresponding to the third public key (with ID 624bd0...) was used to sign the token stored as `TenantAPITokenForKey624` below.

View Source
const TestPubKey = "" /* 458-byte string literal not displayed */
View Source
const TestTenantHeader = "X-Scope-OrgID"

HTTP Request header used by GetTenant when disableAPIAuthentication is true and requireTenantName is nil. This is only meant for use in testing, and lines up with the tenant HTTP header used by Cortex and Loki.

Variables

This section is empty.

Functions

func AuthenticateAnyTenantByHeaderOr401

func AuthenticateAnyTenantByHeaderOr401(w http.ResponseWriter, r *http.Request) (string, bool)

Expect HTTP request to be authenticated. Accept any tenant (identified by name).

Require the tenant authentication token to be presented via the Bearer scheme in the `Authorization` header.

Return 2-tuple `(tenantName: string, ok: bool)`.

If `ok` is `false` then do not use tenant name (it is an empty string).

Write a 401 response to `w` when the authentication proof is not present, in a bad format, or invalid in any way.

Callers can rely on a 401 response to have been emitted when `ok` is `false`.

func AuthenticateSpecificTenantByDDQueryParamOr401

func AuthenticateSpecificTenantByDDQueryParamOr401(
	w http.ResponseWriter,
	r *http.Request,
	expectedTenantName string,
) bool

Expect HTTP request to specify a URL containing the query parameter api_key=<AUTHTOKEN>

Extract and cryptographically verify that authentication token.

Emit error HTTP responses and return `false` upon any failure.

Return `true` only when the authentication proof is valid and matches the expected Opstrace tenant name.

Callers can rely on a 401 response to have been emitted when `ok` is `false`.

func AuthenticateSpecificTenantByHeaderMap

func AuthenticateSpecificTenantByHeaderMap(
	headers map[string][]string,
	headerName string,
	expectedTenantName string,
) error

Expect HTTP or GRPC request to be authenticated. Require that the tenant (identified by its name) matches `expectedTenantName`.

Require the tenant authentication token to be presented via the Bearer scheme in the `Authorization` (or other name as specified by `headerName`) header.

Return `nil` when authentication succeeded, or `error` otherwise.

func AuthenticateSpecificTenantByHeaderOr401

func AuthenticateSpecificTenantByHeaderOr401(w http.ResponseWriter, r *http.Request, expectedTenantName string) bool

Expect HTTP request to be authenticated. Require that the tenant (identified by its name) matches `expectedTenantName`.

Require the tenant authentication token to be presented via the Bearer scheme in the `Authorization` header.

Return `true` when authentication succeeded.

Write a 401 response to `w` when the authentication proof is not present, in a bad format, or invalid in any way. Return `false`.

Callers can rely on a 401 response to have been emitted when `ok` is `false`.

func GetTenantNameOr401

func GetTenantNameOr401(
	w http.ResponseWriter,
	r *http.Request,
	expectedTenantName *string,
	disableAPIAuthentication bool,
) (string, bool)

Infer tenant identity (name) from request or context.

Return 2-tuple (tenantName: string, ok: bool).

Callers can rely on a 401 response to have been emitted when `ok` is `false`, and should terminate request processing. If `ok` is `false` do not use `tenantName`.

When `ok` is true, the request has been inspected and the returned `tenantName` can be used by the caller.

If `disableAPIAuthentication` is `false` and `ok` is `true` then the returned `tenantName` has been read from a validated dOpstrace tenant API authentication token.

If `expectedTenantName` is non-nil, then each request's identity is required to match this tenant. Otherwise, the tenant may vary per-request.

If `disableAPIAuthentication` is `true`, then the `expectedTenantName` or X-Scope-OrgID header value is used (no cryptographic verification, insecure).

For clarity, the four states and their resulting behavior in tabular representation:

expected.. | disableAPIAuthentication | behavior --------------------------------------------------------------------------

 set       |  false (proof required)   | production setting: common
           |                           |   tenant from authn proof must match
           |                           |
 set       |  true (no proof req)      | production setting: not so common
           |                           |   tenant from authn proof is ignored
		   |						   |   (INSECURE)
           |                           |
 not set   |  false (proof req)        | production setting:
           |                           |  deployment accepts requests for more
		   |                           |  than one tenant. tenant name inferred
           |                           |  from (verified) authn proof.
           |                           |
 not set   |  true (no proof req)      | testing setting:
           |                           |  tenant name read from X-Scope-OrgID header

func ReadConfigFromEnvOrCrash

func ReadConfigFromEnvOrCrash()

Read set of public keys for authentication token verification from environment. If key deserialization fails or if no key is configured, log an error and exit the process with a non-zero exit code.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL