garm/auth/context.go
Gabriel Adrian Samfira dd1740c189 Refactor the websocket client and add fixes
The websocket client and hub interaction has been simplified a bit.
The hub now acts only as a tee writer to the various clients that
register. Clients must register and unregister explicitly. The hub
is no longer passed in to the client.

Websocket clients now watch for password changes or jwt token expiration
times. Clients are disconnected if auth token expires or if the password
is changed.

Various aditional safety checks have been added.

Signed-off-by: Gabriel Adrian Samfira <gsamfira@cloudbasesolutions.com>
2024-07-05 12:55:35 +00:00

281 lines
7.7 KiB
Go

// Copyright 2022 Cloudbase Solutions SRL
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package auth
import (
"context"
"time"
runnerErrors "github.com/cloudbase/garm-provider-common/errors"
"github.com/cloudbase/garm/params"
)
type contextFlags string
const (
isAdminKey contextFlags = "is_admin"
fullNameKey contextFlags = "full_name"
readMetricsKey contextFlags = "read_metrics"
// UserIDFlag is the User ID flag we set in the context
UserIDFlag contextFlags = "user_id"
isEnabledFlag contextFlags = "is_enabled"
jwtTokenFlag contextFlags = "jwt_token"
authExpiresFlag contextFlags = "auth_expires"
passwordGenerationFlag contextFlags = "password_generation"
instanceIDKey contextFlags = "id"
instanceNameKey contextFlags = "name"
instancePoolIDKey contextFlags = "pool_id"
instancePoolTypeKey contextFlags = "scope"
instanceEntityKey contextFlags = "entity"
instanceRunnerStatus contextFlags = "status"
instanceTokenFetched contextFlags = "tokenFetched"
instanceHasJITConfig contextFlags = "hasJITConfig"
instanceParams contextFlags = "instanceParams"
)
func SetInstanceID(ctx context.Context, id string) context.Context {
return context.WithValue(ctx, instanceIDKey, id)
}
func InstanceID(ctx context.Context) string {
elem := ctx.Value(instanceIDKey)
if elem == nil {
return ""
}
return elem.(string)
}
func SetInstanceTokenFetched(ctx context.Context, fetched bool) context.Context {
return context.WithValue(ctx, instanceTokenFetched, fetched)
}
func InstanceTokenFetched(ctx context.Context) bool {
elem := ctx.Value(instanceTokenFetched)
if elem == nil {
return false
}
return elem.(bool)
}
func SetInstanceHasJITConfig(ctx context.Context, cfg map[string]string) context.Context {
return context.WithValue(ctx, instanceHasJITConfig, len(cfg) > 0)
}
func InstanceHasJITConfig(ctx context.Context) bool {
elem := ctx.Value(instanceHasJITConfig)
if elem == nil {
return false
}
return elem.(bool)
}
func SetInstanceParams(ctx context.Context, instance params.Instance) context.Context {
return context.WithValue(ctx, instanceParams, instance)
}
func InstanceParams(ctx context.Context) (params.Instance, error) {
elem := ctx.Value(instanceParams)
if elem == nil {
return params.Instance{}, runnerErrors.ErrNotFound
}
instanceParams, ok := elem.(params.Instance)
if !ok {
return params.Instance{}, runnerErrors.ErrNotFound
}
return instanceParams, nil
}
func SetInstanceRunnerStatus(ctx context.Context, val params.RunnerStatus) context.Context {
return context.WithValue(ctx, instanceRunnerStatus, val)
}
func InstanceRunnerStatus(ctx context.Context) params.RunnerStatus {
elem := ctx.Value(instanceRunnerStatus)
if elem == nil {
return params.RunnerPending
}
return elem.(params.RunnerStatus)
}
func SetInstanceName(ctx context.Context, val string) context.Context {
return context.WithValue(ctx, instanceNameKey, val)
}
func InstanceName(ctx context.Context) string {
elem := ctx.Value(instanceNameKey)
if elem == nil {
return ""
}
return elem.(string)
}
func SetInstancePoolID(ctx context.Context, val string) context.Context {
return context.WithValue(ctx, instancePoolIDKey, val)
}
func InstancePoolID(ctx context.Context) string {
elem := ctx.Value(instancePoolIDKey)
if elem == nil {
return ""
}
return elem.(string)
}
func SetInstancePoolType(ctx context.Context, val string) context.Context {
return context.WithValue(ctx, instancePoolTypeKey, val)
}
func InstancePoolType(ctx context.Context) string {
elem := ctx.Value(instancePoolTypeKey)
if elem == nil {
return ""
}
return elem.(string)
}
func SetInstanceEntity(ctx context.Context, val string) context.Context {
return context.WithValue(ctx, instanceEntityKey, val)
}
func InstanceEntity(ctx context.Context) string {
elem := ctx.Value(instanceEntityKey)
if elem == nil {
return ""
}
return elem.(string)
}
func PopulateInstanceContext(ctx context.Context, instance params.Instance) context.Context {
ctx = SetInstanceID(ctx, instance.ID)
ctx = SetInstanceName(ctx, instance.Name)
ctx = SetInstancePoolID(ctx, instance.PoolID)
ctx = SetInstanceRunnerStatus(ctx, instance.RunnerStatus)
ctx = SetInstanceTokenFetched(ctx, instance.TokenFetched)
ctx = SetInstanceHasJITConfig(ctx, instance.JitConfiguration)
ctx = SetInstanceParams(ctx, instance)
return ctx
}
// PopulateContext sets the appropriate fields in the context, based on
// the user object
func PopulateContext(ctx context.Context, user params.User, authExpires *time.Time) context.Context {
ctx = SetUserID(ctx, user.ID)
ctx = SetAdmin(ctx, user.IsAdmin)
ctx = SetIsEnabled(ctx, user.Enabled)
ctx = SetFullName(ctx, user.FullName)
ctx = SetExpires(ctx, authExpires)
ctx = SetPasswordGeneration(ctx, user.Generation)
return ctx
}
func SetExpires(ctx context.Context, expires *time.Time) context.Context {
if expires == nil {
return ctx
}
return context.WithValue(ctx, authExpiresFlag, expires)
}
func Expires(ctx context.Context) *time.Time {
elem := ctx.Value(authExpiresFlag)
if elem == nil {
return nil
}
return elem.(*time.Time)
}
func SetPasswordGeneration(ctx context.Context, val uint) context.Context {
return context.WithValue(ctx, passwordGenerationFlag, val)
}
func PasswordGeneration(ctx context.Context) uint {
elem := ctx.Value(passwordGenerationFlag)
if elem == nil {
return 0
}
return elem.(uint)
}
// SetFullName sets the user full name in the context
func SetFullName(ctx context.Context, fullName string) context.Context {
return context.WithValue(ctx, fullNameKey, fullName)
}
// FullName returns the full name from context
func FullName(ctx context.Context) string {
name := ctx.Value(fullNameKey)
if name == nil {
return ""
}
return name.(string)
}
// SetIsEnabled sets a flag indicating if account is enabled
func SetIsEnabled(ctx context.Context, enabled bool) context.Context {
return context.WithValue(ctx, isEnabledFlag, enabled)
}
// IsEnabled returns the a boolean indicating if the enabled flag is
// set and is true or false
func IsEnabled(ctx context.Context) bool {
elem := ctx.Value(isEnabledFlag)
if elem == nil {
return false
}
return elem.(bool)
}
// SetAdmin sets the isAdmin flag on the context
func SetAdmin(ctx context.Context, isAdmin bool) context.Context {
return context.WithValue(ctx, isAdminKey, isAdmin)
}
// IsAdmin returns a boolean indicating whether
// or not the context belongs to a logged in user
// and if that context has the admin flag set
func IsAdmin(ctx context.Context) bool {
elem := ctx.Value(isAdminKey)
if elem == nil {
return false
}
return elem.(bool)
}
// SetUserID sets the userID in the context
func SetUserID(ctx context.Context, userID string) context.Context {
return context.WithValue(ctx, UserIDFlag, userID)
}
// UserID returns the userID from the context
func UserID(ctx context.Context) string {
userID := ctx.Value(UserIDFlag)
if userID == nil {
return ""
}
return userID.(string)
}
// GetAdminContext will return an admin context. This can be used internally
// when fetching users.
func GetAdminContext(ctx context.Context) context.Context {
if ctx == nil {
ctx = context.Background()
}
ctx = SetUserID(ctx, "")
ctx = SetAdmin(ctx, true)
ctx = SetIsEnabled(ctx, true)
return ctx
}