Skip to content
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

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
Draft

core: Spiffe Utils #11522

wants to merge 13 commits into from

Conversation

erm-g
Copy link
Contributor

@erm-g erm-g commented Sep 13, 2024

No description provided.

Copy link
Contributor

@matthewstevenson88 matthewstevenson88 left a 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) {
Copy link
Contributor

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)
Copy link
Contributor

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() {}
Copy link
Contributor

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.

Copy link
Contributor Author

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;
Copy link
Contributor

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?

Copy link
Contributor Author

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

Copy link
Contributor

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 {
Copy link
Contributor

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' "
Copy link
Contributor

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?

Copy link
Contributor Author

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?

Copy link
Contributor

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;
Copy link
Contributor

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 {
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

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());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this allowed?

Copy link
Contributor Author

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?

Copy link
Contributor

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");
Copy link
Contributor

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))) {
Copy link
Contributor

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) {
Copy link
Contributor

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;
Copy link
Contributor

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;
Copy link
Contributor

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,
Copy link
Contributor

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());
Copy link
Contributor

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",
Copy link
Contributor

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' "
Copy link
Contributor

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) {
Copy link
Contributor

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");
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants