Implement annotation validation (#9673)
* Add validation to all annotations * Add annotation validation for fcgi * Fix reviews and fcgi e2e * Add flag to disable cross namespace validation * Add risk, flag for validation, tests * Add missing formating * Enable validation by default on tests * Test validation flag * remove ajp from list * Finalize validation changes * Add validations to CI * Update helm docs * Fix code review * Use a better name for annotation risk
This commit is contained in:
parent
86c00a2310
commit
c5f348ea2e
109 changed files with 4320 additions and 586 deletions
|
|
@ -32,13 +32,56 @@ import (
|
|||
"k8s.io/ingress-nginx/pkg/util/file"
|
||||
)
|
||||
|
||||
const (
|
||||
authSecretTypeAnnotation = "auth-secret-type" //#nosec G101
|
||||
authRealmAnnotation = "auth-realm"
|
||||
authTypeAnnotation = "auth-type"
|
||||
// This should be exported as it is imported by other packages
|
||||
AuthSecretAnnotation = "auth-secret" //#nosec G101
|
||||
)
|
||||
|
||||
var (
|
||||
authTypeRegex = regexp.MustCompile(`basic|digest`)
|
||||
authTypeRegex = regexp.MustCompile(`basic|digest`)
|
||||
authSecretTypeRegex = regexp.MustCompile(`auth-file|auth-map`)
|
||||
|
||||
// AuthDirectory default directory used to store files
|
||||
// to authenticate request
|
||||
AuthDirectory = "/etc/ingress-controller/auth"
|
||||
)
|
||||
|
||||
var AuthSecretConfig = parser.AnnotationConfig{
|
||||
Validator: parser.ValidateRegex(*parser.BasicCharsRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium, // Medium as it allows a subset of chars
|
||||
Documentation: `This annotation defines the name of the Secret that contains the usernames and passwords which are granted access to the paths defined in the Ingress rules. `,
|
||||
}
|
||||
|
||||
var authSecretAnnotations = parser.Annotation{
|
||||
Group: "authentication",
|
||||
Annotations: parser.AnnotationFields{
|
||||
AuthSecretAnnotation: AuthSecretConfig,
|
||||
authSecretTypeAnnotation: {
|
||||
Validator: parser.ValidateRegex(*authSecretTypeRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation what is the format of auth-secret value. Can be "auth-file" that defines the content of an htpasswd file, or "auth-map" where each key
|
||||
is a user and each value is the password.`,
|
||||
},
|
||||
authRealmAnnotation: {
|
||||
Validator: parser.ValidateRegex(*parser.CharsWithSpace, false),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskMedium, // Medium as it allows a subset of chars
|
||||
Documentation: `This annotation defines the realm (message) that should be shown to user when authentication is requested.`,
|
||||
},
|
||||
authTypeAnnotation: {
|
||||
Validator: parser.ValidateRegex(*authTypeRegex, true),
|
||||
Scope: parser.AnnotationScopeLocation,
|
||||
Risk: parser.AnnotationRiskLow,
|
||||
Documentation: `This annotation defines the basic authentication type. Should be "basic" or "digest"`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const (
|
||||
fileAuth = "auth-file"
|
||||
mapAuth = "auth-map"
|
||||
|
|
@ -85,13 +128,18 @@ func (bd1 *Config) Equal(bd2 *Config) bool {
|
|||
}
|
||||
|
||||
type auth struct {
|
||||
r resolver.Resolver
|
||||
authDirectory string
|
||||
r resolver.Resolver
|
||||
authDirectory string
|
||||
annotationConfig parser.Annotation
|
||||
}
|
||||
|
||||
// NewParser creates a new authentication annotation parser
|
||||
func NewParser(authDirectory string, r resolver.Resolver) parser.IngressAnnotation {
|
||||
return auth{r, authDirectory}
|
||||
return auth{
|
||||
r: r,
|
||||
authDirectory: authDirectory,
|
||||
annotationConfig: authSecretAnnotations,
|
||||
}
|
||||
}
|
||||
|
||||
// Parse parses the annotations contained in the ingress
|
||||
|
|
@ -99,7 +147,7 @@ func NewParser(authDirectory string, r resolver.Resolver) parser.IngressAnnotati
|
|||
// and generated an htpasswd compatible file to be used as source
|
||||
// during the authentication process
|
||||
func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
|
||||
at, err := parser.GetStringAnnotation("auth-type", ing)
|
||||
at, err := parser.GetStringAnnotation(authTypeAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -109,12 +157,15 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
}
|
||||
|
||||
var secretType string
|
||||
secretType, err = parser.GetStringAnnotation("auth-secret-type", ing)
|
||||
secretType, err = parser.GetStringAnnotation(authSecretTypeAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
if ing_errors.IsValidationError(err) {
|
||||
return nil, err
|
||||
}
|
||||
secretType = fileAuth
|
||||
}
|
||||
|
||||
s, err := parser.GetStringAnnotation("auth-secret", ing)
|
||||
s, err := parser.GetStringAnnotation(AuthSecretAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if err != nil {
|
||||
return nil, ing_errors.LocationDenied{
|
||||
Reason: fmt.Errorf("error reading secret name from annotation: %w", err),
|
||||
|
|
@ -131,6 +182,13 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
if sns == "" {
|
||||
sns = ing.Namespace
|
||||
}
|
||||
secCfg := a.r.GetSecurityConfiguration()
|
||||
// We don't accept different namespaces for secrets.
|
||||
if !secCfg.AllowCrossNamespaceResources && sns != ing.Namespace {
|
||||
return nil, ing_errors.LocationDenied{
|
||||
Reason: fmt.Errorf("cross namespace usage of secrets is not allowed"),
|
||||
}
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("%v/%v", sns, sname)
|
||||
secret, err := a.r.GetSecret(name)
|
||||
|
|
@ -140,7 +198,10 @@ func (a auth) Parse(ing *networking.Ingress) (interface{}, error) {
|
|||
}
|
||||
}
|
||||
|
||||
realm, _ := parser.GetStringAnnotation("auth-realm", ing)
|
||||
realm, err := parser.GetStringAnnotation(authRealmAnnotation, ing, a.annotationConfig.Annotations)
|
||||
if ing_errors.IsValidationError(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
passFilename := fmt.Sprintf("%v/%v-%v-%v.passwd", a.authDirectory, ing.GetNamespace(), ing.UID, secret.UID)
|
||||
|
||||
|
|
@ -210,3 +271,12 @@ func dumpSecretAuthMap(filename string, secret *api.Secret) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a auth) GetDocumentation() parser.AnnotationFields {
|
||||
return a.annotationConfig.Annotations
|
||||
}
|
||||
|
||||
func (a auth) Validate(anns map[string]string) error {
|
||||
maxrisk := parser.StringRiskToRisk(a.r.GetSecurityConfiguration().AnnotationsRiskLevel)
|
||||
return parser.CheckAnnotationRisk(anns, maxrisk, authSecretAnnotations.Annotations)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue