Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -166,14 +166,12 @@ func runService(cfg config.Config) error {
var requireLocalScopeCheck = false

if cfg.Auth.Enabled {
// Check for a static policy bearer token before validation so that
// ValidateAuthConfig can accurately skip OAuth2 field checks.
const policyBearerTokenKey = "policy-bearer-token"
_, hasStaticPolicyTokenErr := credentials.ReadTokenFromFile(secretsPath, policyBearerTokenKey)
hasStaticPolicyToken := hasStaticPolicyTokenErr == nil
// Detect self-managed deployment by the presence of the Vault Agent secrets file.
_, hasSecretsFileErr := os.Stat(secretsPath)
hasSecretsFile := hasSecretsFileErr == nil
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

// Validate auth configuration
if err := config.ValidateAuthConfig(cfg.Auth, hasStaticPolicyToken); err != nil {
if err := config.ValidateAuthConfig(cfg.Auth, hasSecretsFile); err != nil {
logger.Error("invalid auth configuration", zap.Error(err))
return err
}
Expand Down Expand Up @@ -237,23 +235,16 @@ func runService(cfg config.Config) error {

var policyClient policy.Authorizer

// If a bearer token exists in the secrets file, use it to authenticate
// outbound calls to the policy evaluator when no OAuth2 token issuer is available.
// Otherwise fall back to the OAuth2 client-credentials flow.
if hasStaticPolicyToken {
logger.Warn("using static bearer token for policy evaluator",
// Use a static (no-auth) client when the Vault Agent secrets file is present,
// indicating a self-managed deployment. Otherwise fall back to OAuth2.
if hasSecretsFile {
logger.Warn("secrets file detected, using static client for policy evaluator",
zap.String("secrets_path", secretsPath))

tokenReader, err := credentials.NewBearerTokenReader(secretsPath, policyBearerTokenKey)
if err != nil {
logger.Error("failed to create bearer token reader", zap.Error(err))
return fmt.Errorf("failed to create bearer token reader: %w", err)
}

policyClient = policy.NewStaticBearerClient(
cfg.Auth.Policy.PolicyEvaluatorAddr,
policyConfig,
tokenReader,
nil,
middleware.GetSharedHTTPClient(&cfg.HTTP),
)
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ func TestValidateAuthConfig_PolicyProvider(t *testing.T) {
tests := []struct {
name string
cfg AuthConfig
hasStaticPolicyToken bool
hasSecretsFile bool
expectedErr error
}{
{
Expand Down Expand Up @@ -252,9 +252,9 @@ func TestValidateAuthConfig_PolicyProvider(t *testing.T) {
expectedErr: ErrInvalidPolicyCredsRefreshInterval,
},
{
// When a static policy token is available, OAuth2 fields are not required.
name: "valid config with static policy token - oauth2 fields not required",
hasStaticPolicyToken: true,
// When the secrets file is present (self-managed), OAuth2 fields are not required.
name: "valid config with secrets file - oauth2 fields not required",
hasSecretsFile: true,
cfg: AuthConfig{
Enabled: true,
Provider: "policy",
Expand All @@ -268,9 +268,9 @@ func TestValidateAuthConfig_PolicyProvider(t *testing.T) {
expectedErr: nil,
},
{
// Even with a static policy token, the always-required fields are still checked.
name: "static policy token does not bypass namespace check",
hasStaticPolicyToken: true,
// Even with a secrets file present, always-required fields are still checked.
name: "secrets file does not bypass namespace check",
hasSecretsFile: true,
cfg: AuthConfig{
Enabled: true,
Provider: "policy",
Expand All @@ -286,7 +286,7 @@ func TestValidateAuthConfig_PolicyProvider(t *testing.T) {

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := ValidateAuthConfig(tt.cfg, tt.hasStaticPolicyToken)
err := ValidateAuthConfig(tt.cfg, tt.hasSecretsFile)
if tt.expectedErr != nil {
require.Error(t, err)
assert.Equal(t, tt.expectedErr, err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ func (p PolicyConfig) WithDefaults() PolicyConfig {
}

// ValidateAuthConfig validates the authentication configuration.
// hasStaticPolicyToken indicates whether a usable static bearer token was found
// in the secrets file; when true the OAuth2 credential fields are not required.
func ValidateAuthConfig(cfg AuthConfig, hasStaticPolicyToken bool) error {
// hasSecretsFile indicates whether the Vault Agent secrets file is present,
// signalling a self-managed deployment where OAuth2 credential fields are not required.
func ValidateAuthConfig(cfg AuthConfig, hasSecretsFile bool) error {
if !cfg.Enabled {
return nil
}
Expand Down Expand Up @@ -135,7 +135,7 @@ func ValidateAuthConfig(cfg AuthConfig, hasStaticPolicyToken bool) error {
if cfg.Policy.PolicyFQDN == "" {
return ErrMissingPolicyFQDN
}
if !hasStaticPolicyToken {
if !hasSecretsFile {
if cfg.Policy.CredsFile == "" {
return ErrMissingPolicyCredsFile
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,18 @@ import (
)

// staticBearerClient implements Authorizer for self-managed deployments where no
// OAuth2 token issuer is available. It authenticates outbound calls to the policy
// evaluator using a static bearer token read from the Vault Agent secrets file.
// OAuth2 token issuer is available. If tokenReader is non-nil, it sets an
// Authorization: Bearer header on outbound calls; otherwise no auth header is sent.
type staticBearerClient struct {
evaluatorAddr string
policyCfg *PolicyConfig
tokenReader *credentials.BearerTokenReader
httpClient *http.Client
}

// NewStaticBearerClient creates an Authorizer that presents a static bearer token
// when calling the policy evaluator, instead of performing a live OAuth2 exchange.
// NewStaticBearerClient creates an Authorizer for self-managed deployments.
// Pass a non-nil tokenReader to authenticate outbound calls with a static bearer
// token; pass nil when the destination service requires no authentication.
func NewStaticBearerClient(
evaluatorAddr string,
policyCfg *PolicyConfig,
Expand Down Expand Up @@ -74,7 +75,9 @@ func (c *staticBearerClient) Evaluate(ctx context.Context, req *pdpv1.RuleReques

httpReq.Header.Set("Content-Type", "application/json")
httpReq.Header.Set("Accept", "application/json")
httpReq.Header.Set("Authorization", "Bearer "+c.tokenReader.Token())
if c.tokenReader != nil {
httpReq.Header.Set("Authorization", "Bearer "+c.tokenReader.Token())
}

resp, err := c.httpClient.Do(httpReq)
if err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,33 +57,48 @@ func TestStaticBearerClient_Evaluate(t *testing.T) {

tests := []struct {
name string
tokenReader *credentials.BearerTokenReader
serverResponse any
serverStatus int
wantErr bool
checkAuth bool
wantAuthHeader string
checkURL bool
}{
{
name: "sends correct Authorization header and URL",
name: "with token reader sends Authorization header and correct URL",
serverStatus: http.StatusOK,
serverResponse: allowResponse,
checkAuth: true,
wantAuthHeader: "Bearer test-bearer-token",
checkURL: true,
},
{
name: "returns parsed response on success",
name: "without token reader sends no Authorization header",
tokenReader: nil,
serverStatus: http.StatusOK,
serverResponse: allowResponse,
wantAuthHeader: "",
},
{
name: "returns error on non-200 status",
serverStatus: http.StatusForbidden,
name: "with token reader returns error on non-200 status",
serverStatus: http.StatusForbidden,
wantAuthHeader: "Bearer test-bearer-token",
wantErr: true,
},
{
name: "without token reader returns error on non-200 status",
tokenReader: nil,
serverStatus: http.StatusInternalServerError,
wantErr: true,
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tokenReader := tc.tokenReader
if tc.wantAuthHeader != "" {
tokenReader = newTestTokenReader(t, "test-bearer-token")
}

var capturedAuth, capturedURL string

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -97,7 +112,7 @@ func TestStaticBearerClient_Evaluate(t *testing.T) {
defer srv.Close()

cfg := &PolicyConfig{Namespace: "event-ledger", PolicyFQDN: "apikey.allow"}
client := NewStaticBearerClient(srv.URL, cfg, newTestTokenReader(t, "test-bearer-token"), &http.Client{})
client := NewStaticBearerClient(srv.URL, cfg, tokenReader, &http.Client{})

req := &pdpv1.RuleRequest{
Namespace: "event-ledger",
Expand All @@ -112,9 +127,7 @@ func TestStaticBearerClient_Evaluate(t *testing.T) {
}
require.NoError(t, err)

if tc.checkAuth {
assert.Equal(t, "Bearer test-bearer-token", capturedAuth)
}
assert.Equal(t, tc.wantAuthHeader, capturedAuth)
if tc.checkURL {
assert.Equal(t, "/v1/namespaces/event-ledger/evaluations/apikey.allow", capturedURL)
}
Expand Down
Loading