Documentation
¶
Overview ¶
Package security provides security-related functionality for the opampcommander application.
Index ¶
- Constants
- Variables
- func GetUserID(ctx *gin.Context, userUsecase userport.UserUsecase) (uuid.UUID, error)
- func NewAuthJWTMiddleware(service *Service) gin.HandlerFunc
- func NewAuthorizationMiddleware(rbacUsecase userport.RBACUsecase, userUsecase userport.UserUsecase, ...) gin.HandlerFunc
- func SetUser(ctx *gin.Context, user *User)
- type AdminSettings
- type AdminUsecase
- type BasicAuthSettings
- type Config
- type JWTSettings
- type LoginResult
- type OAuth2Usecase
- type OAuthSettings
- type OAuthStateClaims
- type OPAMPClaims
- type PasswordHasher
- type Service
- func (s *Service) AllowedRedirectHosts() []string
- func (s *Service) AuthCodeURL(cliRedirect string) (string, error)
- func (s *Service) BasicAuth(ctx context.Context, username, password string) (LoginResult, error)
- func (s *Service) CLIRedirectFromState(state string) (string, error)
- func (s *Service) DeviceAuth(ctx context.Context) (*oauth2.DeviceAuthResponse, error)
- func (s *Service) Exchange(ctx context.Context, state, code string) (LoginResult, error)
- func (s *Service) ExchangeDeviceAuth(ctx context.Context, deviceCode string, expiry time.Time) (LoginResult, error)
- func (s *Service) Refresh(refreshToken string) (LoginResult, error)
- func (s *Service) ValidateToken(tokenString string) (*OPAMPClaims, error)
- type UnsupportedTokenTypeError
- type Usecase
- type User
Constants ¶
const ( // TokenTypeAccess marks a JWT issued as an access token. TokenTypeAccess = "access" // TokenTypeRefresh marks a JWT issued as a refresh token, accepted only by the refresh endpoint. TokenTypeRefresh = "refresh" )
Token type values stored in the OPAMPClaims.TokenType claim.
const ( // StateLength defines the length of the state string to be generated for OAuth2 authentication. StateLength = 16 // Length of the state string to be generated for OAuth2 authentication. )
Variables ¶
var ( // ErrInvalidState is returned when the state parameter is invalid. ErrInvalidState = errors.New("invalid state parameter") // ErrStateExpired is returned when the state parameter has expired. ErrStateExpired = errors.New("state parameter has expired") // ErrInvalidToken is returned when the provided token is invalid. ErrInvalidToken = errors.New("invalid token") // ErrInvalidEmail is returned when the email in the token claims is invalid. ErrInvalidEmail = errors.New("invalid email in token claims") // ErrInvalidTokenClaims is returned when the token claims are invalid. ErrInvalidTokenClaims = errors.New("invalid token claims") // ErrTokenExpired is returned when the token has expired. ErrTokenExpired = errors.New("token has expired") // ErrInvalidUsernameOrPassword is returned when the provided username or password is invalid. ErrInvalidUsernameOrPassword = errors.New("invalid username or password") // ErrBasicAuthDisabled is returned when basic-auth password operations are attempted but no // pepper is configured, which disables DB-backed basic-auth user creation and login. ErrBasicAuthDisabled = errors.New("basic auth is disabled: no password pepper configured") // ErrNoPrimaryEmailFound is returned when no primary email is found in the user's emails. ErrNoPrimaryEmailFound = errors.New("no primary verified email found") // ErrOAuth2ClientCreationFailed is returned when the OAuth2 client creation fails. ErrOAuth2ClientCreationFailed = errors.New("failed to create OAuth2 client") )
var ( // ErrNilContext is returned when the context is nil. ErrNilContext = errors.New("nil context") // ErrInvalidContext is returned when the context is not a valid Gin context. ErrInvalidContext = errors.New("invalid context") // ErrInvalidUserInContext is returned when the user in the context is not valid. ErrInvalidUserInContext = errors.New("invalid user in context") )
Functions ¶
func NewAuthJWTMiddleware ¶
func NewAuthJWTMiddleware( service *Service, ) gin.HandlerFunc
NewAuthJWTMiddleware creates a new Gin middleware for JWT authentication.
func NewAuthorizationMiddleware ¶
func NewAuthorizationMiddleware( rbacUsecase userport.RBACUsecase, userUsecase userport.UserUsecase, adminEmail string, logger *slog.Logger, ) gin.HandlerFunc
NewAuthorizationMiddleware creates a Gin middleware that enforces RBAC for both namespace-scoped (/api/v1/namespaces/:namespace/*) and global (/api/v1/users, /api/v1/roles, /api/v1/servers) resources. The adminEmail user bypasses all RBAC checks.
Types ¶
type AdminSettings ¶
type AdminSettings struct {
// Username is the username for the admin user.
Username string
// Password is the password for the admin user.
Password string
// Email is the email address for the admin user.
// This is used in jwt claims.
Email string
}
AdminSettings holds the configuration settings for admin authentication. This is used for basic authentication of the admin user.
type AdminUsecase ¶
type AdminUsecase interface {
// BasicAuth authenticates the user using basic authentication with username and password.
BasicAuth(ctx context.Context, username, password string) (LoginResult, error)
}
AdminUsecase defines the use case for admin authentication.
type BasicAuthSettings ¶
type BasicAuthSettings struct {
// Pepper is a server-side secret mixed into every basic-auth password hash.
// It must be a long, random value set once and kept stable for the lifetime of the
// stored credentials. When empty, basic-auth user creation and password login are disabled.
Pepper string
}
BasicAuthSettings holds the configuration settings for DB-backed basic-auth users.
type Config ¶
type Config struct {
// JWTSettings holds the configuration settings for JSON Web Tokens (JWT).
JWTSettings JWTSettings
// AdminSettings holds the configuration settings for admin authentication.
AdminSettings AdminSettings
// BasicAuthSettings holds the configuration settings for DB-backed basic-auth users.
BasicAuthSettings BasicAuthSettings
// OAuthSettings holds the configuration settings for OAuth2 authentication.
OAuthSettings *OAuthSettings
}
Config holds the authentication configuration consumed by the security Service. It is owned by the security package so that callers do not depend on the aggregate config package; the config package composes this into its settings.
type JWTSettings ¶
type JWTSettings struct {
SigningKey string
Issuer string
Expiration time.Duration
// RefreshExpiration is the lifetime of refresh tokens issued alongside access tokens.
// Zero means refresh tokens are disabled.
RefreshExpiration time.Duration
Audience []string
}
JWTSettings holds the configuration settings for JSON Web Tokens (JWT).
type LoginResult ¶
type LoginResult struct {
Token string
RefreshToken string
ExpiresAt time.Time
Email string
Groups []string // provider group/org memberships, added as labels to the user on login
}
LoginResult contains the result of a successful authentication.
type OAuth2Usecase ¶
type OAuth2Usecase interface {
// AuthCodeURL generates the OAuth2 authorization URL with a unique state parameter.
// cliRedirect is an optional loopback redirect URI (e.g. http://127.0.0.1:PORT/callback)
// to be encoded into the state JWT; on callback the server redirects to it instead of
// returning JSON. Empty string preserves the legacy JSON behavior.
AuthCodeURL(cliRedirect string) (string, error)
// CLIRedirectFromState extracts the loopback redirect URI from a state JWT, if any.
CLIRedirectFromState(state string) (string, error)
// Exchange exchanges the OAuth2 authorization code for an access token.
Exchange(ctx context.Context, state, code string) (LoginResult, error)
}
OAuth2Usecase defines the use case for OAuth2 authentication.
type OAuthSettings ¶
type OAuthSettings struct {
// ClientID is the OAuth2 client ID for GitHub authentication.
ClientID string
// Secret is the OAuth2 client secret for GitHub authentication.
Secret string
// CallbackURL is the URL to which GitHub will redirect after authentication.
CallbackURL string
// AllowedRedirectHosts is the list of additional hostnames that the
// /api/v1/auth/github/authcode endpoint will accept as redirect targets,
// in addition to the always-allowed loopback hosts (127.0.0.1, ::1,
// localhost). Use this to allowlist the web UI deployment (e.g.
// "opampcommander-alpha.minuk.dev"). Empty by default.
AllowedRedirectHosts []string
// JWTSettings holds the JWT configuration settings.
// This is used for the state parameter in OAuth2 authentication.
JWTSettings JWTSettings
}
OAuthSettings holds the configuration settings for GitHub OAuth2 authentication.
type OAuthStateClaims ¶
type OAuthStateClaims struct {
jwt.RegisteredClaims
// CLIRedirect, when non-empty, signals the callback handler to redirect the browser
// to this loopback URI (with token query params) instead of returning JSON.
CLIRedirect string `json:"cliRedirect,omitempty"`
}
OAuthStateClaims defines the custom claims for the JWT token used for the state parameter in OAuth2 authentication.
type OPAMPClaims ¶
type OPAMPClaims struct {
jwt.RegisteredClaims
Email string `json:"email"`
// TokenType is "access" or "refresh". Empty value is treated as "access" for backward compatibility.
TokenType string `json:"tokenType,omitempty"`
}
OPAMPClaims defines the custom claims for the JWT token used for opampcommander's authentication. It includes the user's email and standard JWT registered claims.
type PasswordHasher ¶
type PasswordHasher struct {
// contains filtered or unexported fields
}
PasswordHasher hashes and verifies basic-auth passwords.
Each password is first combined with a server-side pepper via HMAC-SHA256 and then hashed with bcrypt (which adds a per-user random salt). The pepper is held in a memguard Enclave so the secret is kept in locked, encrypted memory and only decrypted transiently during an operation. When no pepper is configured the hasher is disabled and every operation returns ErrBasicAuthDisabled.
func NewPasswordHasher ¶
func NewPasswordHasher(cfg *Config) *PasswordHasher
NewPasswordHasher builds a PasswordHasher from the security config. When the configured pepper is empty the returned hasher is disabled: Hash and Verify return ErrBasicAuthDisabled. The plaintext pepper is sealed into a memguard Enclave and the caller-visible config string is the only remaining plaintext copy.
func (*PasswordHasher) Enabled ¶
func (h *PasswordHasher) Enabled() bool
Enabled reports whether basic-auth password operations are available (a pepper is configured).
func (*PasswordHasher) Hash ¶
func (h *PasswordHasher) Hash(password string) (string, error)
Hash returns a one-way hash of the password, peppered and salted, suitable for persistence. Returns ErrBasicAuthDisabled when no pepper is configured.
func (*PasswordHasher) Verify ¶
func (h *PasswordHasher) Verify(password, hash string) error
Verify checks a plaintext password against a stored hash. It returns nil on a match, ErrBasicAuthDisabled when no pepper is configured, and a non-nil error otherwise (including a wrong password).
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service provides security-related functionality for the opampcommander application.
func New ¶
func New( logger *slog.Logger, settings *Config, httpClient *http.Client, passwordHasher *PasswordHasher, userPort userport.UserPersistencePort, ) *Service
New creates a new instance of the Service struct with the provided logger and OAuth settings.
func (*Service) AllowedRedirectHosts ¶
AllowedRedirectHosts returns the configured extra hosts that the authcode endpoint will accept as redirect targets (in addition to loopback hosts).
func (*Service) AuthCodeURL ¶
AuthCodeURL generates the OAuth2 authorization URL with a unique state parameter. If cliRedirect is non-empty, it is encoded into the state JWT and used by the callback handler to redirect to a loopback URL instead of returning JSON.
func (*Service) BasicAuth ¶
BasicAuth authenticates the user using basic authentication with username and password. It first checks the config-provided admin credentials, then falls back to DB-backed basic-auth users (verified against their stored password hash with the configured pepper).
func (*Service) CLIRedirectFromState ¶
CLIRedirectFromState parses the state JWT and returns the embedded CLI loopback redirect, if any. Returns an empty string when the state has no CLI redirect.
func (*Service) DeviceAuth ¶
DeviceAuth initiates the OAuth2 device authorization flow. It returns a device authorization response that contains the user code and verification URL.
func (*Service) Exchange ¶
Exchange exchanges the OAuth2 authorization code for an access token. It validates the state parameter to prevent CSRF attacks.
func (*Service) ExchangeDeviceAuth ¶
func (s *Service) ExchangeDeviceAuth( ctx context.Context, deviceCode string, expiry time.Time, ) (LoginResult, error)
ExchangeDeviceAuth exchanges the device code for an access token. It retrieves the user's primary email from GitHub and creates a JWT token with the email as a claim. It returns the JWT token string or an error if the process fails.
func (*Service) Refresh ¶
func (s *Service) Refresh(refreshToken string) (LoginResult, error)
Refresh validates a refresh token and issues a new access/refresh token pair for the same user. It rotates the refresh token (the old one remains valid until its own expiry — JWTs are stateless).
func (*Service) ValidateToken ¶
func (s *Service) ValidateToken(tokenString string) (*OPAMPClaims, error)
ValidateToken validates the provided JWT token string and returns the claims if valid. It checks the token's validity, expiration, and rejects refresh tokens.
type UnsupportedTokenTypeError ¶
type UnsupportedTokenTypeError struct {
TokenType string
}
UnsupportedTokenTypeError is returned when the token type is not supported.
func (*UnsupportedTokenTypeError) Error ¶
func (e *UnsupportedTokenTypeError) Error() string
type Usecase ¶
type Usecase interface {
// ValidateToken validates the provided JWT token string and returns the claims if valid.
ValidateToken(tokenString string) (*OPAMPClaims, error)
// Refresh exchanges a valid refresh token for a new access token (and rotated refresh token).
Refresh(refreshToken string) (LoginResult, error)
// AdminUsecase returns the use case for admin authentication.
AdminUsecase
// OAuth2Usecase returns the use case for OAuth2 authentication.
OAuth2Usecase
}
Usecase defines the use case for the security package.
type User ¶
type User struct {
// Authenticated indicates if the user is authenticated
Authenticated bool
// Email is the primary email of the user
// If the user is not authenticated, this will be nil
Email *string
}
User represents a user in the system.
func NewAnonymousUser ¶
func NewAnonymousUser() *User
NewAnonymousUser creates a new anonymous user. Some operations needs an user (e.g., for audit logging) even if the user is not authenticated.