-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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
core: Spiffe Utils #11522
base: master
Are you sure you want to change the base?
core: Spiffe Utils #11522
Conversation
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.
Thanks for putting this together @erm-g! I took an initial pass and left some comments. Please give me a ping when it's ready for another look. :)
checkArgument(checkNotNull(certChain, "certChain").length > 0, "CertChain can't be empty"); | ||
Collection<List<?>> subjectAltNames = certChain[0].getSubjectAlternativeNames(); | ||
if (subjectAltNames != null) { | ||
for (List<?> altName : subjectAltNames) { |
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.
Please ensure that there is a unique URI SAN.
|
||
private SpiffeUtil() {} | ||
|
||
public static Optional<SpiffeId> extractSpiffeId(X509Certificate[] certChain) |
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.
Please add a Javadoc.
private static final Integer URI_SAN_TYPE = 6; | ||
private static final String USE_PARAMETER_VALUE = "x509-svid"; | ||
|
||
private SpiffeUtil() {} |
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.
nit: Please move to the bottom of the class.
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.
Dropped it completely, similar to #11490 (comment)
|
||
public static class TrustBundle { | ||
|
||
private final Map<String, Long> sequenceNumbers; |
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.
What are the sequence numbers? Could you explain why they're relevant here?
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.
In short it's a number that indicates the bundle version for a particular TB, however it's not mandatory -
https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Trust_Domain_and_Bundle.md#411-sequence-number
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.
Thanks for explaining. Are we going to use the sequence numbers for anything? If not, should we drop them?
} | ||
} | ||
|
||
public static class TrustBundle { |
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.
Please add a Javadoc for this class and its methods.
for (Map<String, ?> keyNode : keysNode) { | ||
String kid = JsonUtil.getString(keyNode, "kid"); | ||
if (kid != null && !kid.equals("")) { | ||
log.log(Level.SEVERE, String.format("'kid' parameter must not be set but value '%s' " |
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.
Here and throughout this method: since this is a library, should we throw an exception rather than logging?
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.
We still need to construct a map even if we can't process some of the entries, i.e json like
domain: google.com
cert:correctBytes
domain: google.com.test
cert:corruptedBytes
should still produce a valid entry for google.com. WDYT?
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.
Ah I see, good question. I was thinking that we should be strict for the start and fail if anything is malformed and, if we need to change in the future, we can do so without breaking users. However, if we start off allowing malformed entries, then we can't disallow them in the future without a breaking change. WDYT?
|
||
private final Map<String, Long> sequenceNumbers; | ||
|
||
private final Map<String, List<X509Certificate>> trustBundleMap; |
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.
nit: Prefer ImmutableMap and ImmutableList.
return Optional.absent(); | ||
} | ||
|
||
public static TrustBundle loadTrustBundleFromFile(String trustBundleFile) throws IOException { |
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.
Could you point me to the spec you're using as a reference for this method?
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.
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.
Thanks! What's confusing me is the following - the code and the examples seem to suggest that the file has the following format:
{
"trust_domains": {
"trust_domain": {
// JWK set
}
}
}
But the spec only seems to talk about the structure of the JWK set. Where is the rest of the structure specified?
Apologies in advance if I'm missing something obvious. :)
for (String trustDomainName : trustDomainsNode.keySet()) { | ||
Map<String, ?> domainNode = JsonUtil.getObject(trustDomainsNode, trustDomainName); | ||
if (domainNode == null || domainNode.size() == 0) { | ||
trustBundleMap.put(trustDomainName, Collections.emptyList()); |
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.
Is this allowed?
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.
From connection status perspective it's the same. However, from monitoring/debugging perspective I think it's useful to differentiate between 'Hey, the file we loaded doesn't contain the domain' and 'Hey, the the domain doesn't have the correct root to validate this conn'.
WDYT?
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.
Makes sense. I'm surprised that the spec allows an empty key or empty value, but agree with you that it doesn't matter from an authentication point of view.
trustDomainName)); | ||
break; | ||
} | ||
String rawCert = JsonUtil.getString(keyNode, "x5c"); |
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.
Is this DER-encoded or PEM-encoded?
https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Trust_Domain_and_Bundle.md#appendix-a-spiffe-bundle-example suggests the former, but all of our discussions have been slanted to the latter.
if (subjectAltNames != null) { | ||
boolean spiffeFound = false; | ||
for (List<?> altName : subjectAltNames) { | ||
if (URI_SAN_TYPE.equals(altName.get(0))) { |
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.
nit: Should we check that altName.length > 0
?
throws CertificateParsingException { | ||
checkArgument(checkNotNull(certChain, "certChain").length > 0, "CertChain can't be empty"); | ||
Collection<List<?>> subjectAltNames = certChain[0].getSubjectAlternativeNames(); | ||
if (subjectAltNames != null) { |
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.
Readability suggestion: To minimize nesting, consider returning early here, e.g.
if (subjectAltNames == null) {
return Optional.absent();
}
checkArgument(checkNotNull(certChain, "certChain").length > 0, "CertChain can't be empty"); | ||
Collection<List<?>> subjectAltNames = certChain[0].getSubjectAlternativeNames(); | ||
if (subjectAltNames != null) { | ||
boolean spiffeFound = false; |
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.
WDYT of storing the SPIFFE ID instead of a boolean, so we don't need to iterate over the list twice?
For example,
Optional<String> uriSan = Optional.absent();
for (List<?> altName : subjectAltNames) {
if (altName.length > 0 && altName.get(0).equals(URI_SAN_TYPE) {
if (uriSan.absent()) {
uriSan = Optional.of(altName.get(0));
} else {
throw exception
}
}
}
|
||
public static class TrustBundle { | ||
|
||
private final Map<String, Long> sequenceNumbers; |
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.
Thanks for explaining. Are we going to use the sequence numbers for anything? If not, should we drop them?
private static final String USE_PARAMETER_VALUE = "x509-svid"; | ||
|
||
/** | ||
* Parses a leaf certificate from the chain to extract unique SPIFFE ID. In case of success, |
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.
Suggested edit: "Returns the SPIFFE ID from the leaf certificate, if present."
for (String trustDomainName : trustDomainsNode.keySet()) { | ||
Map<String, ?> domainNode = JsonUtil.getObject(trustDomainsNode, trustDomainName); | ||
if (domainNode == null || domainNode.size() == 0) { | ||
trustBundleMap.put(trustDomainName, Collections.emptyList()); |
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.
Makes sense. I'm surprised that the spec allows an empty key or empty value, but agree with you that it doesn't matter from an authentication point of view.
"e": "AQAB" | ||
}, | ||
{ | ||
"kty": "RSA", |
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.
Should we add the x5c
key so that we can confirm in our tests that multiple certs for the same trust domain get properly captured in the spiffe bundle?
for (Map<String, ?> keyNode : keysNode) { | ||
String kid = JsonUtil.getString(keyNode, "kid"); | ||
if (kid != null && !kid.equals("")) { | ||
log.log(Level.SEVERE, String.format("'kid' parameter must not be set but value '%s' " |
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.
Ah I see, good question. I was thinking that we should be strict for the start and fail if anything is malformed and, if we need to change in the future, we can do so without breaking users. However, if we start off allowing malformed entries, then we can't disallow them in the future without a breaking change. WDYT?
try { | ||
Collection<? extends Certificate> certs = CertificateFactory.getInstance("X509") | ||
.generateCertificates(stream); | ||
if (certs.size() > 0) { |
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.
Could this be > 1? If yes, should we take those into account? What does the spec say about this case?
String trustDomainName) { | ||
List<X509Certificate> result = new ArrayList<>(); | ||
for (Map<String, ?> keyNode : keysNode) { | ||
String kid = JsonUtil.getString(keyNode, "kid"); |
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.
optional: To make the code more readable, consider moving the checks on the "kid" and "use" claims into a helper function.
No description provided.