-
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
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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= |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
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 parsable and follows Notary V2 rules | ||
func validateDistinguishedName(name string) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO, this method should move into On second thought, this is Notary V2 specific DN validation based on what we allow in trust policy, so seems appropriate here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this method and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These helpers are for policy validation but I wanted to move them to a helper file so that policy.go is cleaner and deals with higher level functionality like policy verification. There is no side effect of having these methods in a helper class as Go package is a closure of everything under the package directory. |
||
mandatoryFields := []string{"C", "ST", "O"} | ||
rDnCount := make(map[string]int) | ||
dn, err := ldapv3.ParseDN(name) | ||
|
||
if err != nil { | ||
return 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 { | ||
for _, attribute := range rdn.Attributes { | ||
rDnCount[attribute.Type]++ | ||
} | ||
} | ||
|
||
// Verify there are no duplicate RDNs (multi-valdued RDNs are not supported) | ||
for key := range rDnCount { | ||
if rDnCount[key] > 1 { | ||
return fmt.Errorf("distinguished name (DN) %q has duplicate RDN attribute for %q, DN can only have unique RDN attributes", name, key) | ||
} | ||
} | ||
|
||
// Verify mandatory fields are present | ||
for _, field := range mandatoryFields { | ||
if rDnCount[field] != 1 { | ||
return 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 nil | ||
} |
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