ContainerImage.Pinniped/internal/idtransform/identity_transformations.go
Ryan Richard 185bcb6c8c Add identity transformation packages idtransform and celformer
Implements Supervisor identity transformations helpers using CEL.
2023-02-06 16:53:08 -08:00

73 lines
3.1 KiB
Go

// Copyright 2023 the Pinniped contributors. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
// Package idtransform defines upstream-to-downstream identity transformations which could be
// implemented using various approaches or languages.
package idtransform
import (
"context"
"fmt"
"strings"
)
// TransformationResult is the result of evaluating a transformation against some inputs.
type TransformationResult struct {
Username string // the new username for an allowed auth
Groups []string // the new group names for an allowed auth
AuthenticationAllowed bool // when false, disallow this authentication attempt
RejectedAuthenticationMessage string // should be set when AuthenticationAllowed is false
}
// IdentityTransformation is an individual identity transformation which can be evaluated.
type IdentityTransformation interface {
Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error)
}
// TransformationPipeline is a list of identity transforms, which can be evaluated in order against some given input
// values.
type TransformationPipeline struct {
transforms []IdentityTransformation
}
// NewTransformationPipeline creates an empty TransformationPipeline.
func NewTransformationPipeline() *TransformationPipeline {
return &TransformationPipeline{transforms: []IdentityTransformation{}}
}
// AppendTransformation adds a transformation to the end of the list of transformations for this pipeline.
// This is not thread-safe, so be sure to add all transformations from a single goroutine before using Evaluate
// from multiple goroutines.
func (p *TransformationPipeline) AppendTransformation(t IdentityTransformation) {
p.transforms = append(p.transforms, t)
}
// Evaluate runs the transformation pipeline for a given input identity. It returns a potentially transformed or
// rejected identity, or an error. If any transformation in the list rejects the authentication, then the list is
// short-circuited but no error is returned. Only unexpected errors are returned as errors. This is safe to call
// from multiple goroutines.
func (p *TransformationPipeline) Evaluate(ctx context.Context, username string, groups []string) (*TransformationResult, error) {
accumulatedResult := &TransformationResult{
Username: username,
Groups: groups,
AuthenticationAllowed: true,
}
var err error
for i, transform := range p.transforms {
accumulatedResult, err = transform.Evaluate(ctx, accumulatedResult.Username, accumulatedResult.Groups)
if err != nil {
// There was an unexpected error evaluating a transformation.
return nil, fmt.Errorf("identity transformation at index %d: %w", i, err)
}
if !accumulatedResult.AuthenticationAllowed {
// Auth has been rejected by a policy. Stop evaluating the rest of the transformations.
return accumulatedResult, nil
}
if strings.TrimSpace(accumulatedResult.Username) == "" {
return nil, fmt.Errorf("identity transformation returned an empty username, which is not allowed")
}
}
// There were no unexpected errors and no policy which rejected auth.
return accumulatedResult, nil
}