Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
44 changes: 44 additions & 0 deletions internal/gatewayapi/securitypolicy.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,12 @@
}
}

basicAuth := p.Spec.BasicAuth
if basicAuth != nil {
if err := validateBasicAuth(basicAuth); err != nil {
return err
}

Check warning on line 370 in internal/gatewayapi/securitypolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/securitypolicy.go#L369-L370

Added lines #L369 - L370 were not covered by tests
}
return nil
}

Expand All @@ -378,6 +384,16 @@
return nil
}

// validateBasicAuth validates the BasicAuth configuration.
// Currently, we only validate that the secret exists, but we don't validate
// the content of the secret. This function will be called when the security policy
// is being processed, but before the secret is actually read.
func validateBasicAuth(basicAuth *egv1a1.BasicAuth) error {
// The actual validation of the htpasswd format will happen when the secret is read
// in the buildBasicAuth function.
return nil
}

func resolveSecurityPolicyGatewayTargetRef(
policy *egv1a1.SecurityPolicy,
target gwapiv1a2.LocalPolicyTargetReferenceWithSectionName,
Expand Down Expand Up @@ -1309,13 +1325,41 @@
usersSecret.Namespace, usersSecret.Name)
}

// Validate the htpasswd format
if err := validateHtpasswdFormat(usersSecretBytes); err != nil {
return nil, err
}

Check warning on line 1331 in internal/gatewayapi/securitypolicy.go

View check run for this annotation

Codecov / codecov/patch

internal/gatewayapi/securitypolicy.go#L1330-L1331

Added lines #L1330 - L1331 were not covered by tests

return &ir.BasicAuth{
Name: irConfigName(policy),
Users: usersSecretBytes,
ForwardUsernameHeader: basicAuth.ForwardUsernameHeader,
}, nil
}

// validateHtpasswdFormat validates that the htpasswd data is in the correct format.
// Currently, only the SHA format is supported by Envoy.
func validateHtpasswdFormat(data []byte) error {
lines := strings.Split(string(data), "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line == "" {
continue
}

parts := strings.SplitN(line, ":", 2)
if len(parts) != 2 {
return fmt.Errorf("invalid htpasswd format: each line must be in the format 'username:password'")
}

password := parts[1]
if !strings.HasPrefix(password, "{SHA}") {
return fmt.Errorf("unsupported htpasswd format: please use {SHA}")
}
}
return nil
}

func (t *Translator) buildExtAuth(
policy *egv1a1.SecurityPolicy,
resources *resource.Resources,
Expand Down
54 changes: 54 additions & 0 deletions internal/gatewayapi/securitypolicy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -660,3 +660,57 @@ func Test_OIDC_PassThroughAuthHeader(t *testing.T) {
func ToPointer[T any](v T) *T {
return &v
}

func Test_validateHtpasswdFormat(t *testing.T) {
tests := []struct {
name string
htpasswd string
wantError bool
}{
{
name: "valid htpasswd with SHA format",
htpasswd: "user1:{SHA}hashed_user1_password\nuser2:{SHA}hashed_user2_password",
wantError: false,
},
{
name: "valid htpasswd with SHA format and empty lines",
htpasswd: "user1:{SHA}hashed_user1_password\n\nuser2:{SHA}hashed_user2_password\n",
wantError: false,
},
{
name: "invalid htpasswd with missing SHA prefix",
htpasswd: "user1:hashed_user1_password",
wantError: true,
},
{
name: "invalid htpasswd with MD5 format",
htpasswd: "user1:$apr1$hashed_user1_password",
wantError: true,
},
{
name: "invalid htpasswd with bcrypt format",
htpasswd: "user1:$2y$hashed_user1_password",
wantError: true,
},
{
name: "invalid htpasswd with missing colon",
htpasswd: "user1{SHA}hashed_user1_password",
wantError: true,
},
{
name: "mixed valid and invalid formats",
htpasswd: "user1:{SHA}hashed_user1_password\nuser2:$apr1$hashed_user2_password",
wantError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validateHtpasswdFormat([]byte(tt.htpasswd))
if (err != nil) != tt.wantError {
t.Errorf("validateHtpasswdFormat() error = %v, wantErr %v", err, tt.wantError)
return
}
})
}
}
Loading