scitokens

package module
v0.3.3 Latest Latest
Warning

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

Go to latest
Published: Nov 5, 2024 License: Apache-2.0 Imports: 13 Imported by: 1

README

scitokens-go Go Reference

WORK IN PROGRESS library for handling SciTokens and WLCG tokens from Go, based on github.com/lestrrat-go/jwx libraries.

Included is a scitoken-validate command-line tool that uses the library to print and validate SciTokens with various criteria, see its README for installation and usage documentation.

The Enforcer API is believed to be stable, but breaking changes may still occur until version 1.0.0 is released.

Usage

To fetch and add the library to your Go project dependencies, run:

go get github.com/scitokens/scitokens-go

Then import it in your source:

import (
	scitokens "github.com/scitokens/scitokens-go"
)
Parsing SciTokens

Note: if you're only interested in validating tokens in a service, you can skip to the next section Validating Tokens, since the Enforcer abstracts away these details.

The SciToken interface is a light wrapper around the general Token interface from the github.com/lestrrat-go/jwx/jwt package, providing convenience methods for parsing and accessing SciToken-specific claims. After parsing the token into a jwt.Token you can convert it to an object implementing the SciToken interface with NewSciToken().

// PrintSciToken prints SciToken information to stdout, without doing any
// verification or validation of the token or its claims.
func PrintSciToken(tok []byte) error {
	jt, err := jwt.Parse(tok)
	if err != nil {
		return err
	}
	st, err := scitokens.NewSciToken(jt)
	if err != nil {
		return err
	}
	fmt.Println(st.Subject())
	fmt.Println(st.Issuer())
	fmt.Println(st.Scopes())
	fmt.Println(st.Groups())
	return nil
}
Validating Tokens

The Enforcer interface defines methods used to verify and validate tokens. Instantiate an enforcer with either NewEnforcer() for one-shot/throwaway use, or NewEnforcerDaemon() for long-lived processes. Both require one or more supported issuer URLs, and NewEnforcerDaemon additionally requires a context that defines the lifetime of the Enforcer and its background goroutines.

enf, err := scitokens.NewEnforcer("https://example.com")
if err != nil {
	log.Fatalf("failed to initialize enforcer: %s", err)
}

An enforcer instantiated with NewEnforcer() will fetch signing keys on-demand when a token is validated.

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
enf, err := scitokens.NewEnforcerDaemon(ctx, "https://example.com")
if err != nil {
	log.Fatalf("failed to initialize enforcer: %s", err)
}

An Enforcer instantiated with NewEnforcerDaemon() will fetch and cache the signing keys from the issuer, and start a goroutine that will routinely refresh the keys until the context is cancelled. Additional issuers can be added later with AddIssuer().

The enforcer provides a ValidateToken() method that verifies and validates a raw encoded token, and several convenience methods for validating tokens from a number of sources, including HTTP requests (ValidateTokenRequest()), and the execution environment (ValidateTokenEnvironment()).

By default the enforcer will verify that the token was signed by a trusted issuer, and that it passes basic validation criteria such as dates. It is not possible to directly parse a SciToken without performing these basic validation checks, this is by design, although it could change in the future if there is a good use case (the ValidateToken... function signatures and behavior won't change though).

Additional validation criteria can be attached to the enforcer with the following methods:

  • RequireAudience(), which takes a string representing the service's URL or some other identifier. It will check the tokens have this audience, or one of the standard wildcard audiences, currently "ANY" or "https://wlcg.cern.ch/jwt/v1/any". If not specified, the token audience will not be checked at all.

  • RequireScope(), which takes a Scope object that the token must have in the scope claim (pathed scopes will match exactly or for a hierarchical parent).

  • RequireGroup(), which takes a group name that must be in the wlcg.groups claim (group name must match exactly, but the leading slash is optional).

if err := enf.RequireAudience("https://example.com"); err != nil {
	log.Fatal(err)
}
if err := enf.RequireScope(scitokens.Scope{"compute.read", ""}); err != nil {
	log.Fatal(err)
}
if err := enf.RequireGroup("cms"); err != nil {
	log.Fatal(err)
}

Criteria set this way will apply to all future ValidateToken... calls. It's also possible to pass additional request-specific validation criteria to the ValidateToken... functions.

if _, err := enf.ValidateToken(tok, scitokens.WithGroup("cms/production")); err == nil {
	doRequest()
} else {
	e := &scitokens.TokenValidationError{}
	if !errors.As(err, &e) {
		// some internal error while parsing/validating the token
		log.Error(err)
	} else {
		// token is not valid, err (and e.Err) will say why.
		log.Debugf("access dened: %v", err)
	}
	denyRequest(err)
}

This example also demonstrates using errors.As() to check if the returned error is specifically a TokenValidationError due to the token not meeting some criteria, or some other internal error, which you may want to handle differently.

The ValidateToken... functions return a SciToken that can be inspected directly or passed to Validate() to test different criteria.

// request is valid if token has either /cms/production or /cms/operations group
if st, err := enf.ValidateToken(tok, scitokens.WithGroup("cms/production")); err == nil {
	doRequest()
} else if enf.Validate(st, scitokens.WithGroup("cms/operations")) == nil {
	doRequest()
} else {
	denyRequest(err)
}

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	NotSciTokenError   = errors.New("token is not a SciToken")
	TokenNotFoundError = errors.New("token not found")
	ScopeParseError    = errors.New("unable to unmarshal and parse scope claim")
	GroupParseError    = errors.New("unable to unmarshal wlcg.groups claim")
	VersionParseError  = errors.New("unable to unmarshal ver claim")
)
View Source
var AnyAudiences = []string{
	"ANY",
	"https://wlcg.cern.ch/jwt/v1/any",
}

AnyAudiences is the list of special wildcard audiences that a token can present to be used anywhere that otherwise accepts it.

"ANY" for SciTokens per https://scitokens.org/technical_docs/Claims.html.

"https://wlcg.cern.ch/jwt/v1/any" for WLCG tokens per https://zenodo.org/record/3460258.

Functions

func GetGroups

func GetGroups(t jwt.Token) ([]string, error)

GetGroups parses the wlcg.groups claim and returns a list of all groups, or an empty list if the wlcg.groups claim is missing.

Returns GroupParseError if the wlcg.groups claim cannot be unmarshaled.

func GetVersion added in v0.2.0

func GetVersion(t jwt.Token) (string, error)

GetVersion retrieves the ver claim, or an empty string if the claim is missing. For compatability it will also look for the wlcg.ver claim, which will be returned as "wlcg:$ver", where $ver is the value of the claim.

func PrintToken

func PrintToken(w io.Writer, t jwt.Token)

PrintToken pretty-prints the token claims to w.

Types

type Enforcer

type Enforcer interface {
	AddIssuer(context.Context, string) error
	RequireAudience(string) error
	RequireScope(Scope) error
	RequireGroup(string) error
	RequireValidator(Validator) error
	Validate(SciToken, ...Validator) error
	ValidateToken([]byte, ...Validator) (SciToken, error)
	ValidateTokenString(string, ...Validator) (SciToken, error)
	ValidateTokenReader(io.Reader, ...Validator) (SciToken, error)
	ValidateTokenEnvironment(...Validator) (SciToken, error)
	ValidateTokenForm(url.Values, string, ...Validator) (SciToken, error)
	ValidateTokenHeader(http.Header, string, ...Validator) (SciToken, error)
	ValidateTokenRequest(*http.Request, ...Validator) (SciToken, error)
}

Enforcer verifies that SciTokens https://scitokens.org are valid, from a certain issuer, and that they allow the requested resource.

func NewEnforcer

func NewEnforcer(issuers ...string) (Enforcer, error)

NewEnforcer initializes a new enforcer for validating SciTokens from the provided issuer(s). Keys are fetched on-demand when a token is verified. Use NewEnforcerDaemon() for long-running processes.

func NewEnforcerDaemon added in v0.3.0

func NewEnforcerDaemon(ctx context.Context, issuers ...string) (Enforcer, error)

NewEnforcerDaemon initializes a new enforcer for validating SciTokens from the provided issuer(s), caching and refreshing keys periodically. The context object should be cancelled when the process is done with the enforcer.

type SciToken

type SciToken interface {
	jwt.Token
	Scopes() []Scope
	Groups() []string
	Version() string
}

SciToken wraps a standard JWT token to add custom claims. Use NewSciToken() to wrap a jwt.Token and parse the custom claims.

func NewSciToken

func NewSciToken(t jwt.Token) (SciToken, error)

NewSciToken wraps a jwt.Token, populating the SciToken from the custom claims.

type Scope

type Scope struct {
	Auth string
	Path string
}

Scope represents a token authorization scope, with optional path.

func GetScopes

func GetScopes(t jwt.Token) ([]Scope, error)

GetScopes parses the scope claim and returns a list of all scopes, or an empty list if the scope claim is missing.

Returns ScopeParseError if the scope claim cannot be unmarshaled or parsed.

func ParseScope

func ParseScope(s string) Scope

ParseScope parses a scope string like AUTHZ[:PATH].

func (Scope) Allowed

func (s Scope) Allowed(operation string, resource string) bool

Allowed returns true if operation on resource (can be empty string) is allowed by this scope. If resource is a sub-path under the scope's path then it is allowed, e.g. if the scope path is write:/baz then operation=write and path=/baz/qux is allowed.

func (Scope) String

func (s Scope) String() string

String returns the string representation of the scope.

type TokenValidationError

type TokenValidationError struct {
	Err error
}

func (*TokenValidationError) Error

func (e *TokenValidationError) Error() string

func (*TokenValidationError) Unwrap

func (e *TokenValidationError) Unwrap() error

type Validator

type Validator interface {
	jwt.Validator
}

Validator describes the interface to validate a SciToken. Right now it's just a convenience wrapper around jwt.Validator.

func WithAudience added in v0.2.0

func WithAudience(audience string) Validator

WithAudience validates that the token has the given audience or one of the supported "any" audiences, as defined in the AnyAudiences package variable.

func WithGroup

func WithGroup(group string) Validator

WithGroup validates that the token contains the group (exactly, leading slash optional) in wlcg.groups.

func WithScope

func WithScope(scope Scope) Validator

WithScope validates that the token is allowed to perform the scopes operation on the scopes path or a sub-path.

Directories

Path Synopsis
cmd

Jump to

Keyboard shortcuts

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