🧪 test(bdd): admin metadata endpoint security property — no secret leak (#52)
Co-authored-by: Gabriel Radureau <arcodange@gmail.com> Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
This commit was merged in pull request #52.
This commit is contained in:
@@ -822,3 +822,61 @@ func (s *JWTRetentionSteps) andSuggestRemediationSteps() error {
|
||||
// Verify remediation suggestions
|
||||
return godog.ErrPending
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// Admin metadata introspection steps (PR #51 + this scenario)
|
||||
// =====================================================================
|
||||
|
||||
// iAddASecondaryJWTSecretNamed adds a secret with a specific value via the
|
||||
// admin API. Used by the admin-introspection scenario to verify that the
|
||||
// metadata endpoint returns metadata only, not the secret value.
|
||||
func (s *JWTRetentionSteps) iAddASecondaryJWTSecretNamed(secretValue string) error {
|
||||
s.SetLastSecret(secretValue)
|
||||
return s.client.Request("POST", "/api/v1/admin/jwt/secrets", map[string]string{
|
||||
"secret": secretValue,
|
||||
"is_primary": "false",
|
||||
})
|
||||
}
|
||||
|
||||
// iRequestTheJWTSecretsMetadataEndpoint hits GET /api/v1/admin/jwt/secrets.
|
||||
func (s *JWTRetentionSteps) iRequestTheJWTSecretsMetadataEndpoint() error {
|
||||
return s.client.Request("GET", "/api/v1/admin/jwt/secrets", nil)
|
||||
}
|
||||
|
||||
// theMetadataShouldContainNSecrets verifies the response count field.
|
||||
func (s *JWTRetentionSteps) theMetadataShouldContainNSecrets(expected int) error {
|
||||
body := string(s.client.GetLastBody())
|
||||
expectedFragment := `"count":` + strconv.Itoa(expected)
|
||||
if !strings.Contains(body, expectedFragment) {
|
||||
return fmt.Errorf("expected response to contain %q, got: %s", expectedFragment, body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// theMetadataShouldNotContainTheSecretValue is the SECURITY-CRITICAL
|
||||
// assertion. If the response contains the raw secret string anywhere,
|
||||
// the endpoint has leaked. This is the property the metadata-only design
|
||||
// is supposed to guarantee.
|
||||
func (s *JWTRetentionSteps) theMetadataShouldNotContainTheSecretValue(secretValue string) error {
|
||||
body := string(s.client.GetLastBody())
|
||||
if strings.Contains(body, secretValue) {
|
||||
return fmt.Errorf("SECURITY: response leaked the secret value %q (response body: %s)", secretValue, body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// everySecretInTheMetadataShouldHaveASHA256Fingerprint asserts the
|
||||
// secret_sha256 field is present and non-empty for each entry. Cheap
|
||||
// regex-style check on the JSON body.
|
||||
func (s *JWTRetentionSteps) everySecretInTheMetadataShouldHaveASHA256Fingerprint() error {
|
||||
body := string(s.client.GetLastBody())
|
||||
// Expect at least one occurrence of "secret_sha256":"<non-empty>"
|
||||
if !strings.Contains(body, `"secret_sha256":"`) {
|
||||
return fmt.Errorf("response does not include any secret_sha256 fingerprint: %s", body)
|
||||
}
|
||||
// Reject obviously-empty values
|
||||
if strings.Contains(body, `"secret_sha256":""`) {
|
||||
return fmt.Errorf("at least one secret_sha256 fingerprint is empty in response: %s", body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -173,6 +173,12 @@ func InitializeAllSteps(ctx *godog.ScenarioContext, client *testserver.Client, s
|
||||
ctx.Step(`^I should receive configuration validation error$`, sc.jwtRetentionSteps.iShouldReceiveConfigurationValidationError)
|
||||
ctx.Step(`^the error should mention "([^"]*)"$`, sc.jwtRetentionSteps.theErrorShouldMention)
|
||||
ctx.Step(`^I have enabled Prometheus metrics$`, sc.jwtRetentionSteps.iHaveEnabledPrometheusMetrics)
|
||||
// Admin metadata introspection steps (PR #51 + admin-introspection scenario)
|
||||
ctx.Step(`^I add a secondary JWT secret "([^"]*)"$`, sc.jwtRetentionSteps.iAddASecondaryJWTSecretNamed)
|
||||
ctx.Step(`^I request the JWT secrets metadata endpoint$`, sc.jwtRetentionSteps.iRequestTheJWTSecretsMetadataEndpoint)
|
||||
ctx.Step(`^the metadata should contain (\d+) secrets$`, sc.jwtRetentionSteps.theMetadataShouldContainNSecrets)
|
||||
ctx.Step(`^the metadata should NOT contain the secret value "([^"]*)"$`, sc.jwtRetentionSteps.theMetadataShouldNotContainTheSecretValue)
|
||||
ctx.Step(`^every secret in the metadata should have a SHA-256 fingerprint$`, sc.jwtRetentionSteps.everySecretInTheMetadataShouldHaveASHA256Fingerprint)
|
||||
ctx.Step(`^I should see "([^"]*)" metric increment$`, sc.jwtRetentionSteps.iShouldSeeMetricIncrement)
|
||||
ctx.Step(`^I should see "([^"]*)" metric decrease$`, sc.jwtRetentionSteps.iShouldSeeMetricDecrease)
|
||||
ctx.Step(`^I should see "([^"]*)" histogram update$`, sc.jwtRetentionSteps.iShouldSeeHistogramUpdate)
|
||||
|
||||
Reference in New Issue
Block a user