package validation
import (
"fmt"
"strings"
"gopkg.in/ldap.v2"
"k8s.io/kubernetes/pkg/util/validation/field"
"github.com/openshift/origin/pkg/auth/ldaputil"
"github.com/openshift/origin/pkg/cmd/server/api"
)
func ValidateLDAPSyncConfig(config *api.LDAPSyncConfig) ValidationResults {
validationResults := ValidationResults{}
validationResults.Append(ValidateStringSource(config.BindPassword, field.NewPath("bindPassword")))
bindPassword, _ := api.ResolveStringValue(config.BindPassword)
validationResults.Append(ValidateLDAPClientConfig(config.URL, config.BindDN, bindPassword, config.CA, config.Insecure, nil))
schemaConfigsFound := []string{}
if config.RFC2307Config != nil {
configResults := ValidateRFC2307Config(config.RFC2307Config)
validationResults.AddErrors(configResults.Errors...)
validationResults.AddWarnings(configResults.Warnings...)
schemaConfigsFound = append(schemaConfigsFound, "rfc2307")
}
if config.ActiveDirectoryConfig != nil {
configResults := ValidateActiveDirectoryConfig(config.ActiveDirectoryConfig)
validationResults.AddErrors(configResults.Errors...)
validationResults.AddWarnings(configResults.Warnings...)
schemaConfigsFound = append(schemaConfigsFound, "activeDirectory")
}
if config.AugmentedActiveDirectoryConfig != nil {
configResults := ValidateAugmentedActiveDirectoryConfig(config.AugmentedActiveDirectoryConfig)
validationResults.AddErrors(configResults.Errors...)
validationResults.AddWarnings(configResults.Warnings...)
schemaConfigsFound = append(schemaConfigsFound, "augmentedActiveDirectory")
}
if len(schemaConfigsFound) > 1 {
validationResults.AddErrors(field.Invalid(field.NewPath("schema"), config, fmt.Sprintf("only one schema-specific config is allowed; found %v", schemaConfigsFound)))
}
if len(schemaConfigsFound) == 0 {
validationResults.AddErrors(field.Required(field.NewPath("schema"), fmt.Sprintf("exactly one schema-specific config is required; one of %v", []string{"rfc2307", "activeDirectory", "augmentedActiveDirectory"})))
}
return validationResults
}
func ValidateLDAPClientConfig(url, bindDN, bindPassword, CA string, insecure bool, fldPath *field.Path) ValidationResults {
validationResults := ValidationResults{}
if len(url) == 0 {
validationResults.AddErrors(field.Required(fldPath.Child("url"), ""))
return validationResults
}
u, err := ldaputil.ParseURL(url)
if err != nil {
validationResults.AddErrors(field.Invalid(fldPath.Child("url"), url, err.Error()))
return validationResults
}
// Make sure bindDN and bindPassword are both set, or both unset
// Both unset means an anonymous bind is used for search (https://tools.ietf.org/html/rfc4513#section-5.1.1)
// Both set means the name/password simple bind is used for search (https://tools.ietf.org/html/rfc4513#section-5.1.3)
if (len(bindDN) == 0) != (len(bindPassword) == 0) {
validationResults.AddErrors(field.Invalid(fldPath.Child("bindDN"), bindDN,
"bindDN and bindPassword must both be specified, or both be empty"))
validationResults.AddErrors(field.Invalid(fldPath.Child("bindPassword"), "(masked)",
"bindDN and bindPassword must both be specified, or both be empty"))
}
if insecure {
if u.Scheme == ldaputil.SchemeLDAPS {
validationResults.AddErrors(field.Invalid(fldPath.Child("url"), url,
fmt.Sprintf("Cannot use %s scheme with insecure=true", u.Scheme)))
}
if len(CA) > 0 {
validationResults.AddErrors(field.Invalid(fldPath.Child("ca"), CA,
"Cannot specify a ca with insecure=true"))
}
} else {
if len(CA) > 0 {
validationResults.AddErrors(ValidateFile(CA, fldPath.Child("ca"))...)
}
}
// Warn if insecure
if insecure {
validationResults.AddWarnings(field.Invalid(fldPath.Child("insecure"), insecure,
"validating passwords over an insecure connection could allow them to be intercepted"))
}
return validationResults
}
func ValidateRFC2307Config(config *api.RFC2307Config) ValidationResults {
validationResults := ValidationResults{}
validationResults.Append(ValidateLDAPQuery(config.AllGroupsQuery, field.NewPath("groupsQuery")))
if len(config.GroupUIDAttribute) == 0 {
validationResults.AddErrors(field.Required(field.NewPath("groupUIDAttribute"), ""))
}
if len(config.GroupNameAttributes) == 0 {
validationResults.AddErrors(field.Required(field.NewPath("groupNameAttributes"), ""))
}
if len(config.GroupMembershipAttributes) == 0 {
validationResults.AddErrors(field.Required(field.NewPath("groupMembershipAttributes"), ""))
}
isUserDNQuery := strings.TrimSpace(strings.ToLower(config.UserUIDAttribute)) == "dn"
validationResults.Append(validateLDAPQuery(config.AllUsersQuery, field.NewPath("usersQuery"), isUserDNQuery))
if len(config.UserUIDAttribute) == 0 {
validationResults.AddErrors(field.Required(field.NewPath("userUIDAttribute"), ""))
}
if len(config.UserNameAttributes) == 0 {
validationResults.AddErrors(field.Required(field.NewPath("userNameAttributes"), ""))
}
return validationResults
}
func ValidateActiveDirectoryConfig(config *api.ActiveDirectoryConfig) ValidationResults {
validationResults := ValidationResults{}
validationResults.Append(ValidateLDAPQuery(config.AllUsersQuery, field.NewPath("usersQuery")))
if len(config.UserNameAttributes) == 0 {
validationResults.AddErrors(field.Required(field.NewPath("userNameAttributes"), ""))
}
if len(config.GroupMembershipAttributes) == 0 {
validationResults.AddErrors(field.Required(field.NewPath("groupMembershipAttributes"), ""))
}
return validationResults
}
func ValidateAugmentedActiveDirectoryConfig(config *api.AugmentedActiveDirectoryConfig) ValidationResults {
validationResults := ValidationResults{}
validationResults.Append(ValidateLDAPQuery(config.AllUsersQuery, field.NewPath("usersQuery")))
if len(config.UserNameAttributes) == 0 {
validationResults.AddErrors(field.Required(field.NewPath("userNameAttributes"), ""))
}
if len(config.GroupMembershipAttributes) == 0 {
validationResults.AddErrors(field.Required(field.NewPath("groupMembershipAttributes"), ""))
}
isGroupDNQuery := strings.TrimSpace(strings.ToLower(config.GroupUIDAttribute)) == "dn"
validationResults.Append(validateLDAPQuery(config.AllGroupsQuery, field.NewPath("groupsQuery"), isGroupDNQuery))
if len(config.GroupUIDAttribute) == 0 {
validationResults.AddErrors(field.Required(field.NewPath("groupUIDAttribute"), ""))
}
if len(config.GroupNameAttributes) == 0 {
validationResults.AddErrors(field.Required(field.NewPath("groupNameAttributes"), ""))
}
return validationResults
}
func ValidateLDAPQuery(query api.LDAPQuery, fldPath *field.Path) ValidationResults {
return validateLDAPQuery(query, fldPath, false)
}
func validateLDAPQuery(query api.LDAPQuery, fldPath *field.Path, isDNOnly bool) ValidationResults {
validationResults := ValidationResults{}
if _, err := ldap.ParseDN(query.BaseDN); err != nil {
validationResults.AddErrors(field.Invalid(fldPath.Child("baseDN"), query.BaseDN,
fmt.Sprintf("invalid base DN for search: %v", err)))
}
if len(query.Scope) > 0 {
if _, err := ldaputil.DetermineLDAPScope(query.Scope); err != nil {
validationResults.AddErrors(field.Invalid(fldPath.Child("scope"), query.Scope,
"invalid LDAP search scope"))
}
}
if len(query.DerefAliases) > 0 {
if _, err := ldaputil.DetermineDerefAliasesBehavior(query.DerefAliases); err != nil {
validationResults.AddErrors(field.Invalid(fldPath.Child("derefAliases"),
query.DerefAliases, "LDAP alias dereferencing instruction invalid"))
}
}
if query.TimeLimit < 0 {
validationResults.AddErrors(field.Invalid(fldPath.Child("timeout"), query.TimeLimit,
"timeout must be equal to or greater than zero"))
}
if isDNOnly {
if len(query.Filter) != 0 {
validationResults.AddErrors(field.Invalid(fldPath.Child("filter"), query.Filter, `cannot specify a filter when using "dn" as the UID attribute`))
}
return validationResults
}
if _, err := ldap.CompileFilter(query.Filter); err != nil {
validationResults.AddErrors(field.Invalid(fldPath.Child("filter"), query.Filter,
fmt.Sprintf("invalid query filter: %v", err)))
}
return validationResults
}