-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add more validations to trust-policy parsing #43
Merged
Merged
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,19 @@ | ||
github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/P4ZwUYSdNCWsMEI0VeyrFoI2rAhJQ= | ||
github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= | ||
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= | ||
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= | ||
github.com/go-ldap/ldap/v3 v3.4.3 h1:JCKUtJPIcyOuG7ctGabLKMgIlKnGumD/iGjuWeEruDI= | ||
github.com/go-ldap/ldap/v3 v3.4.3/go.mod h1:7LdHfVt6iIOESVEe3Bs4Jp2sHEKgDeduAhgM1/f9qmo= | ||
github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= | ||
github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= | ||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= | ||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= | ||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o= | ||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package verification | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"strings" | ||
|
||
ldapv3 "github.com/go-ldap/ldap/v3" | ||
) | ||
|
||
// isPresent is a utility function to check if a string exists in an array | ||
func isPresent(val string, values []string) bool { | ||
for _, v := range values { | ||
if v == val { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
// validateRegistryScopeFormat validates if a scope is following the format defined in distribution spec | ||
func validateRegistryScopeFormat(scope string) error { | ||
// Domain and Repository regexes are adapted from distribution implementation | ||
// https://github.com/distribution/distribution/blob/main/reference/regexp.go#L31 | ||
domainRegexp := regexp.MustCompile(`^(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])(?:(?:\.(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]))+)?(?::[0-9]+)?$`) | ||
repositoryRegexp := regexp.MustCompile(`^[a-z0-9]+(?:(?:(?:[._]|__|[-]*)[a-z0-9]+)+)?(?:(?:/[a-z0-9]+(?:(?:(?:[._]|__|[-]*)[a-z0-9]+)+)?)+)?$`) | ||
errorMessage := "registry scope %q is not valid, make sure it is the fully qualified registry URL without the scheme/protocol. e.g domain.com/my/repository" | ||
firstSlash := strings.Index(scope, "/") | ||
if firstSlash < 0 { | ||
return fmt.Errorf(errorMessage, scope) | ||
} | ||
domain := scope[:firstSlash] | ||
repository := scope[firstSlash+1:] | ||
|
||
if domain == "" || repository == "" || !domainRegexp.MatchString(domain) || !repositoryRegexp.MatchString(repository) { | ||
return fmt.Errorf(errorMessage, scope) | ||
} | ||
|
||
// No errors | ||
return nil | ||
} | ||
|
||
// validateDistinguishedName validates if a DN name is parsable and follows Notary V2 rules | ||
func validateDistinguishedName(name string) (map[string]string, error) { | ||
mandatoryFields := []string{"C", "ST", "O"} | ||
attrKeyValue := make(map[string]string) | ||
dn, err := ldapv3.ParseDN(name) | ||
|
||
if err != nil { | ||
return nil, fmt.Errorf("distinguished name (DN) %q is not valid, it must contain 'C', 'ST', and 'O' RDN attributes at a minimum, and follow RFC 4514 standard", name) | ||
} | ||
|
||
for _, rdn := range dn.RDNs { | ||
|
||
// multi-valued RDNs are not supported (TODO: add spec reference here) | ||
if len(rdn.Attributes) > 1 { | ||
return nil, fmt.Errorf("distinguished name (DN) %q has multi-valued RDN attributes, remove multi-valued RDN attributes as they are not supported", name) | ||
} | ||
for _, attribute := range rdn.Attributes { | ||
if attrKeyValue[attribute.Type] == "" { | ||
attrKeyValue[attribute.Type] = attribute.Value | ||
} else { | ||
return nil, fmt.Errorf("distinguished name (DN) %q has duplicate RDN attribute for %q, DN can only have unique RDN attributes", name, attribute.Type) | ||
} | ||
} | ||
} | ||
|
||
// Verify mandatory fields are present | ||
for _, field := range mandatoryFields { | ||
if attrKeyValue[field] == "" { | ||
return nil, fmt.Errorf("distinguished name (DN) %q has no mandatory RDN attribute for %q, it must contain 'C', 'ST', and 'O' RDN attributes at a minimum", name, field) | ||
} | ||
} | ||
// No errors | ||
return attrKeyValue, nil | ||
} | ||
|
||
func validateOverlappingDNs(policyName string, parsedDNs []parsedDN) error { | ||
for i, dn1 := range parsedDNs { | ||
for j, dn2 := range parsedDNs { | ||
if i != j && isOverlappingDN(dn1.ParsedMap, dn2.ParsedMap) { | ||
return fmt.Errorf("trust policy statement %q has overlapping x509 trustedIdentities, %q overlaps with %q", policyName, dn1.RawString, dn2.RawString) | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func isOverlappingDN(dn1 map[string]string, dn2 map[string]string) bool { | ||
for key := range dn1 { | ||
if dn1[key] != dn2[key] { | ||
return false | ||
} | ||
} | ||
return true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
go-ldap
has a dependency ongithub.com/Azure/go-ntlmssp
, but the functionality we need, DN validation does not. Is there a way to avoid this transitive dependency? Is the transitive dependency dropped during build as we do not use any types fromgithub.com/Azure/go-ntlmssp
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AFAIK, final build will only include the specific methods and their dependencies. Although I could not confirm this with official Go docs, I found multiple online forums confirming this. Seems like https://github.com/jondot/goweight could help us see what's included in the final build. I added it to my TODO list to revisit later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unpopular opinion: Pulling in
github.com/go-ldap/ldap
, which is a library to talk with LDAP servers, just for validating a DN, seems like using a sledgehammer to crack a nut.I would argue it's better just to copy
ldap.ParseDN
, and related tests, intonotation-go
and remove that dependency.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@qmuntal If we consume the whole package in our builds (like in java), then I agree with what you said. Thanks to Go static linking, our build will only include
dn.ParseDN
and it's dependencies. I prefer to keep a dependency ongo-ldap
to ensure we get the updates and security fixes. Also,go-ldap
is the most popular library for ldap in Go, I'm not too worried about having a dependency on it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Besides, I want to give them the credit rather than simply copying their code :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not that concerned about binary size, as Go is really good at prunning unnecessary code. I'm worried about having a sane dependency tree. Keeping it to the bare minimum is always a plus, even if it means duplicating code.
That said, this particular dependency is not a red line for me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One concern I had was, we would see patch updates to this package more frequently from
go-ldap/ldap
package than we'd be actually impacted, as we are using a single type from the whole package. What is generally the process to handle patch updates to dependencies, will dependabot make this simpler?@rgnote it's possible to copy a code files into our repo and still provide correct attribution to source repo.
I'd prefer copying the code in this case, it's not a one way decision, we can change it the other way if we wanted to later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tracking this in #45