-
Notifications
You must be signed in to change notification settings - Fork 478
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
Introduce DevID node attestor #2111
Introduce DevID node attestor #2111
Conversation
Signed-off-by: Marcos Yedro <[email protected]>
func (p *Plugin) FetchAttestationData(stream nodeattestor.NodeAttestor_FetchAttestationDataServer) error { | ||
conf := p.getConfig() | ||
if conf == nil { | ||
return devid.Error("not configured") |
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.
since it is a GRPC call, maybe you can return status.Error here. (same comment for all errors)
for example:
func (p *Plugin) FetchAttestationData(stream nodeattestor.NodeAttestor_FetchAttestationDataServer) error { | ||
conf := p.getConfig() | ||
if conf == nil { | ||
return devid.Error("not configured") |
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.
devid package can be confusing because you are in a devid package, can you set a name like common_devid on import?
// Send attestation request | ||
err = stream.Send(&nodeattestor.FetchAttestationDataResponse{ | ||
AttestationData: &spc.AttestationData{ | ||
Type: devid.PluginName, |
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.
mmm not sure we must have same name for both plugins at least no using the same constant
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.
Hmm, I can't think of any reason why this wouldn't be appropriate. We are already doing it in other attestors as well.
|
||
// If DevID residency verification configured | ||
var credActChallengeResp []byte | ||
if conf.checkDevIDResidency && challenges.CredActivation != 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.
what happens in case checkDevIDResidency == true
and CredActivation == nil
,
is it expected that it is a success challenge?
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 happens in case checkDevIDResidency == true and CredActivation == nil
That state indicates that the agent was configured to perform the proof of residency but the server was configured to not require it. The server doesn't send any credential activation challenge to solve (nil
).
is it expected that it is a success challenge?
I wouldn't say that it is a successful resolution of the challenge. The challenge is not send by the sever, so the agent can (and must) ignore the "solving" function.
} | ||
|
||
func (p *Plugin) Configure(ctx context.Context, req *plugin.ConfigureRequest) (*plugin.ConfigureResponse, error) { | ||
err := devid.ValidateGlobalConfig(req.GlobalConfig) |
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.
global config with trustdomain will be always there, you added it as a belt and suspender approach right?
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.
Yes, I saw other node attestors doing that verification so I wanted to be consistent.
return nil, nil, fmt.Errorf("max attempts reached: %w", err) | ||
} | ||
|
||
log.Printf("TPM was not able to start the command. Retrying: attempt (%d/%d)", i, maxAttempts) |
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.
you must be use some logs here maybe the one that is provided from plugin, but not stdout log.
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! I forgot to add it after a refactor 🤦
// SRKTemplateRSA returns SRK template used during provisioning. | ||
func SRKTemplateRSA() tpm2.Public { | ||
template := tpm2tools.SRKTemplateRSA() | ||
template.RSAParameters.ModulusRaw = []byte{} |
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 possible. that ModulusRaw
has some value? is it expected it to be replaced?
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.
The gotpm library does not have a method to create the default SRK template recommended by the latest version of the specification. So we create the template on our side. I’ll add a comment and rename the function to make this clearer.
According to TCG TPM v2.0 Provisioning Guidance (section 7.5.1) the Storage Root Key (SRK) template should be based on the Endorsement Key (EK) template.
The EK template is defined in TCG EK Credential Profile for TPM Family 2.0 (section B). The latest version of this document (published in july 2020) defined a new template (called H-1) for the RSA EK template.
The new template sets the unique field (named here as ModulusRaw) to an empty buffer instead of a well-known buffer (filled with 0s).
|
||
func validatePluginConfig(extConf *ExternalConfig) error { | ||
// DevID bundle path is always required | ||
if extConf.DevIDBundlePath == "" { |
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.
switch can simplify it
return nil | ||
} | ||
|
||
func verifyDevIDSignature(cert *x509.Certificate, roots *x509.CertPool) error { |
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.
it is used only once, maybe you can avoid creating this function?
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 see your point, but I believe encapsulating this piece of code in a function is still useful to improve code readability.
"2.5.4.17": "POSTALCODE", | ||
} | ||
|
||
func FromCertificate(selectorType, prefix string, cert *x509.Certificate) []*spc.Selector { |
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.
SelectorsFromCertificate?
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.
or move to a package called selectors
Thank you for this @marcosy! I've started reviewing it, will probably take a little longer to complete. I do have one high level question though
I feel like being able to prove that the cert we're using is backed a key that resides in a TPM is the main motivating factor for making this a dedicated plugin rather than a feature in x509pop. If we're not proving this fact to the server, then it's hard for me to see a difference. Is there a reason that someone wouldn't want to do this? |
Thanks for your comments @MarcosDY and @evan2645!
The only reason I can think of for someone not doing this is due to the extra configurations that are needed. To verify that the DevID is backed by a key that resides in a TPM from a particular manufacturer, the user has to additionally provide:
However, we could find an alternative validation method in case the endorsement certificate is not available (e.g. using a public key allowed-list in the server-side plugin configuration) |
- Setting 'common_devid' import name for 'devid' pkg - Create errors using status pkg for gRPC handles - Rename internalConfig/ExternalConfig to config/Config - Change 'if' to 'switch' for config validation - Rename some variables for better readibility - Add some early returns - Clarify comment for EK handle value Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
- Rename functions that get selectors - Change set of 'if' for switch - Replace newCachedKey function for library function - Add comment and rename function that creates SRK from default template Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
I addressed the initial round of comments. Thanks for the early feedback! |
…evid-node-attestor Signed-off-by: Andrew Harding <[email protected]>
Signed-off-by: Andrew Harding <[email protected]>
I've just merged #2180. Consequently, you'll need to touch up the import for the nodeattestor definitions. Old import: If you'd rather me touch it up and push a commit, I'm happy to oblige. Just let me know. |
…ode-attestor Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Thanks for letting me know! I'll update the PR 🙂 |
I’ve been working on some tools to facilitate the testing/use of this plugin.
I still need to add unit tests for this plugin. |
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
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.
This is looking GREAT @marcosy! Huge improvements here structurally, it is much much easier to read and review. Good job! :-D
I left some more comments here, much more narrowly scoped than before but still mostly about naming, typo/error cleanup, the autodetect logic etc. I need to do another pass analyzing the exchanges and flow between agent/server, key usages etc, but I think that overall we're looking pretty good and getting close. Thanks for your continued work on this ❤️
key pair was generated and resides in a TPM. Additionally, the server verifies | ||
that the TPM belongs to a trusted vendor by verifying that the endorsement | ||
certificate is rooted to a trusted set of CAs. |
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 think the primary purpose for doing this is to ensure that the TPM is authentic (as opposed to a software TPM, where the keys may not be protected at all). Assuming my thinking there is correct, I suggest the following
key pair was generated and resides in a TPM. Additionally, the server verifies | |
that the TPM belongs to a trusted vendor by verifying that the endorsement | |
certificate is rooted to a trusted set of CAs. | |
key pair was generated and resides in a TPM. Additionally, the server verifies | |
that the TPM is authentic by verifying that the endorsement certificate is | |
rooted to a trusted set of manufacturer CAs. |
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! Looks clearer in that way, I'll update it.
| Configuration | Description | Default | | ||
| ------------------------- | ----------- | ----------------------- | | ||
| `devid_bundle_path` | The path to the trusted CA bundle on disk for the DevID certificate. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | | | ||
| `endorsement_bundle_path` | The path to the trusted CA bundle on disk for the endorsement certificate. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | | |
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.
Similar to above comments, minor nit on the wording:
| `endorsement_bundle_path` | The path to the trusted CA bundle on disk for the endorsement certificate. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | | | |
| `endorsement_ca_path` | The path to the trusted manufacturer CA certificate(s) on disk. The file must contain one or more PEM blocks forming the set of trusted manufacturer CA's for chain-of-trust verification. | | |
|
||
| Configuration | Description | Default | | ||
| ------------------------- | ----------- | ----------------------- | | ||
| `devid_bundle_path` | The path to the trusted CA bundle on disk for the DevID certificate. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | | |
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 do you think about using ca
instead of bundle
on the naming here? We use the word bundle
a lot, and it frequently means different things ... I feel like ca
is a little bit clearer.
| `devid_bundle_path` | The path to the trusted CA bundle on disk for the DevID certificate. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | | | |
| `devid_ca_path` | The path to the trusted CA certificate(s) on disk to use for DevID validation. The file must contain one or more PEM blocks forming the set of trusted root CA's for chain-of-trust verification. | | |
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 agree, CA looks more precise. I'll update it.
| Selector | Example | Description | | ||
| ---------------------------- | ----------------------------------------------------------------- | --------------------------------- | | ||
| Fingerprint |`tpm_devid:fingerprint:9ba51e2643bea24e91d24bdec3a1aaf8e967b6e5` | The certificate SHA1 fingerprint as a hex string.| | ||
| Certificate serial number |`tpm_devid:certificate:serialnumber:835861456985135479204994168` | The certificate serial 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.
When we are talking about devid selectors, we are pretty much always talking about certs right? Perhaps we can omit the certificate
part here? e.g. tpm_devid:fingerprint:1234
, tpm_devid:serialnumber:1234
, tpm_devid:subject:cn:foo
etc?
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: serial_number
feels a little more consistent since the plugin name is snake case and x509 defines it as serialNumber
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.
sounds good!
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.
Reading your response to my other comment about IDevID vs LDevID, I realize now that perhaps we will have more than one serial_number
type selector in the future if we grow support for IDevID? Maybe instead of omitting certificate
from the selector, it should be replaced with ldevid
?
tpm_devid:ldevid:serial_number:1234
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 agree with your comment below related to selectors. I believe that for this initial version of the attestor we should be good with having the subject common name and the fingerprints (same selectors than x509pop). We can add more selectors later if there is a use case not covered.
Regarding adding a "ldevid"/"idevid" prefix, since probably only one of them will be used at the time, maybe it is not necessary to specify if it is an IDevID or LDevID in the selectors?
| Fingerprint |`tpm_devid:fingerprint:9ba51e2643bea24e91d24bdec3a1aaf8e967b6e5` | The certificate SHA1 fingerprint as a hex string.| | ||
| Certificate serial number |`tpm_devid:certificate:serialnumber:835861456985135479204994168` | The certificate serial number. | | ||
| Subject common name |`tpm_devid:certificate:subject:cn:example.org` | The subject's common name. | | ||
| Subject common name |`tpm_devid:certificate:subject:cn:example.org` | The subject's common name. | |
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.
duplicate?
// Verify DevID residency (if configured) | ||
var nonce []byte | ||
var credActivationChallenge *common_devid.CredActivation | ||
if conf.checkDevIDResidency { |
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 this still exist? I think it is left over?
} | ||
|
||
// Verify credential activation challenge (if configured) | ||
if conf.checkDevIDResidency { |
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.
Same comment here
} | ||
|
||
// Create SPIFFE ID and selectors | ||
certSelectors := selectorsFromCertificate(common_devid.PluginName, "certificate", devIDCert) |
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.
The number of places that we're generating selectors from a cert is growing. We can already see some divergence between this and x509pop. I wonder if we should have a shared function for this. We've been talking about having similar shared functions exposed for other attestors (e.g. k8s attestor podspec -> selectors function).
I think @azdagron may have some thoughts 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.
Sounds good, I can create a shared function and put it in package "pkg/common/selector", what do you think?
|
||
// Create SPIFFE ID and selectors | ||
certSelectors := selectorsFromCertificate(common_devid.PluginName, "certificate", devIDCert) | ||
fingerprint := fingerprint(devIDCert) |
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.
This fingerprint part can also be inside selectorsFromCertificate
if we are going to share with x509pop
"2.5.4.6": "c", // Country | ||
"2.5.4.8": "st", // State | ||
"2.5.4.10": "o", // Organization | ||
"2.5.4.11": "ou", // Organizational unit |
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'd prefer to not deal with codepoints like this. The pkix name struct given by golang x509 certificate has all of this stuff already and we can create selectors from them explicitly?
I also wonder if we really need all of these. Selector naming and count is a general concern, and it would be good to have some clarity on the need before adding a bunch of them. The x509pop attestor emits only fingerprints and cn, and we have not had any request for more than that... it has been there for some time now.
To be clear, I'm all for having more selectors if it's useful to have them. What I'm trying to avoid is supporting selectors that users may be less likely to consume.
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 talked to some folks which are planning to use this plugin internally. They seem to be ok with using the subject common name and fingerprint. So I think that we should be fine generating the same set of selectors that are generated in x509pop (at least for this initial version).
I'll update this.
Signed-off-by: Marcos Yedro <[email protected]>
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.
This is looking great @marcosy!
While I'm doing a more detailed review, I thought to leave a first set of comments.
Awesome job!
// DevID certificate, public and private key are always required | ||
switch { | ||
case c.DevIDCertPath == "": | ||
return fmt.Errorf("devid_cert_path is required") |
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.
No need for formatted error:
return fmt.Errorf("devid_cert_path is required") | |
return errors.New("devid_cert_path is required") |
return fmt.Errorf("devid_cert_path is required") | ||
|
||
case c.DevIDPrivPath == "": | ||
return fmt.Errorf("devid_priv_path is required") |
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.
No need for formatted error:
return fmt.Errorf("devid_priv_path is required") | |
return errors.New("devid_priv_path is required") |
return fmt.Errorf("devid_priv_path is required") | ||
|
||
case c.DevIDPubPath == "": | ||
return fmt.Errorf("devid_pub_path is required") |
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.
No need for formatted error:
return fmt.Errorf("devid_pub_path is required") | |
return errors.New("devid_pub_path is required") |
return "", errors.New("unable to autodetect TPM") | ||
} | ||
|
||
// VerifyTPM verifies that the given path belongs to a functional TPM 2.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.
// VerifyTPM verifies that the given path belongs to a functional TPM 2.0 | |
// isValidTPM verifies that the given path belongs to a functional TPM 2.0 |
} | ||
|
||
default: | ||
return nil, fmt.Errorf("bad key type: 0x%04x", pub.Type) |
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.
0x%04x
is always returned instead of pub.Type
.
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.
Hmm I think it is working ok? https://play.golang.org/p/2ecIQALLuu7
The type is printed in hexadecimal format to make it easier to search for it in the go-tpm constants.
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.
Got it!
} | ||
|
||
if keyFromCert.E != keyFromTemplate.E { | ||
return fmt.Errorf("exponent missmatch") |
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.
No need for formatted error.
There is also a typo:
return fmt.Errorf("exponent missmatch") | |
return errors.New("exponent mismatch") |
} | ||
|
||
if keyFromCert.N.Cmp(keyFromTemplate.N) != 0 { | ||
return fmt.Errorf("modulus missmatch") |
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.
No need for formatted error.
There is also a typo:
return fmt.Errorf("modulus missmatch") | |
return errors.New("modulus mismatch") |
return params.Sign, nil | ||
|
||
default: | ||
return nil, fmt.Errorf("unsupported key type 0x%04x", pub.Type) |
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.
0x%04x
is always returned instead of pub.Type
.
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.
Hmm I think it is working ok? https://play.golang.org/p/2ecIQALLuu7 but maybe I'm missing something
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.
You are right!
|
||
case isRetry(err): | ||
if i == maxAttempts { | ||
return nil, fmt.Errorf("max attempts reached: %w", err) |
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.
It would be useful to include information about the action involved, otherwise the error may be difficult to understand. Maybe something like max attempts reached while trying to sign the given data
.
|
||
case isRetry(err): | ||
if i == maxAttempts { | ||
return nil, nil, fmt.Errorf("max attempts reached: %w", err) |
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 think that here also would benefit from being more detailed about the action that is being performed while the error happened.
|
||
1. A proof-of-possession challenge: This is required to verify the node is in | ||
possession of the private key that corresponds to the DevID certificate. | ||
Additionally, the server verifies that the DevID certificate is rooted to |
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.
Additionally, the server verifies that the DevID certificate is rooted to | |
Additionally, the server verifies that the DevID certificate is rooted to |
} | ||
|
||
var sig *tpm2.Signature | ||
for i := 0; i <= maxAttempts; i++ { |
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 think that this count should start with 1 instead of 0, otherwise the maximum number of attempts will be maxAttempts + 1.
sig, err = tpm2.Sign(k.rw, k.Handle, "", digest, token, nil) | ||
switch { | ||
case err == nil: | ||
break |
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.
This break
statement affects the switch
only, it doesn't terminate the for
loop, that means that the Sign operation will continue to happen until maxAttempts
is reached.
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
Signed-off-by: Marcos Yedro <[email protected]>
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.
Thank you for your patience with this @marcosy!
I've noticed that agent_full.conf
and server_full.conf
are missing updates for the agent and server plugins configs. We should update them.
I left a bunch more comments, but all of them should be easy to address.
return nil, fmt.Errorf("failed to read NV index %08x: %w", EKCertificateHandleRSA, err) | ||
} | ||
|
||
// In some TPMs, when we read bytes from a NV index, the readed content |
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 some TPMs, when we read bytes from a NV index, the readed content | |
// In some TPMs, when we read bytes from an NV index, the content read |
} | ||
|
||
// In some TPMs, when we read bytes from a NV index, the readed content | ||
// includes the DER encoded x509 certificate + trailing data. We need to |
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.
// includes the DER encoded x509 certificate + trailing data. We need to | |
// includes the DER encoded X.509 certificate + trailing data. We need to |
func (c *Session) loadKey(pubKey, privKey []byte, parentKeyPassword, keyPassword string) (*SigningKey, error) { | ||
pub, err := tpm2.DecodePublic(pubKey) | ||
if err != nil { | ||
return nil, fmt.Errorf("tpm2.Public decoding failed: %w", err) |
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.
Maybe tpm2.DecodePublic failed
?
}, | ||
hSession, // policyHandle: Handle for the policy session being extended. | ||
nil, // policyNonce: The policy nonce for the session (can be the Empty Buffer). | ||
nil, // cpHash: Digest of the command parameters to which this authorization is limited. (If it is not limited, the parameter will be the Empty Buffer). |
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.
nil, // cpHash: Digest of the command parameters to which this authorization is limited. (If it is not limited, the parameter will be the Empty Buffer). | |
nil, // cpHash: Digest of the command parameters to which this authorization is limited (if it is not limited, the parameter will be the Empty Buffer). |
Log: p.log, | ||
}) | ||
if err != nil { | ||
return status.Errorf(codes.Internal, "unable start a new TPM session: %v", err) |
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.
return status.Errorf(codes.Internal, "unable start a new TPM session: %v", err) | |
return status.Errorf(codes.Internal, "unable to start a new TPM session: %v", err) |
test/tpmsimulator/simulator.go
Outdated
// certificate and key are generated. | ||
func NewProvisioningCA(c *ProvisioningConf) (*ProvisioningAuthority, error) { | ||
if c == nil { | ||
return nil, fmt.Errorf("provisioning config is 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.
return nil, fmt.Errorf("provisioning config is nil") | |
return nil, errors.New("provisioning config is nil") |
test/tpmsimulator/simulator.go
Outdated
} | ||
|
||
default: | ||
return nil, fmt.Errorf("the root certificate or private key is nil but not both") |
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.
return nil, fmt.Errorf("the root certificate or private key is nil but not both") | |
return nil, errors.New("the root certificate or private key is nil but not both") |
test/tpmsimulator/simulator.go
Outdated
return provisioningAuthority, nil | ||
} | ||
|
||
// Chain returns the leaf and intermediates certificates in DER format |
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.
// Chain returns the leaf and intermediates certificates in DER format | |
// Chain returns the leaf and intermediate certificates in DER format |
test/tpmsimulator/simulator.go
Outdated
return chain | ||
} | ||
|
||
// ChainPem returns the leaf and intermediates certificates in PEM format |
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.
// ChainPem returns the leaf and intermediates certificates in PEM format | |
// ChainPem returns the leaf and intermediate certificates in PEM format |
test/tpmsimulator/simulator.go
Outdated
privateKey = p.RootKey | ||
|
||
default: | ||
return nil, fmt.Errorf("the intermediate certificate or private key is nil but not both") |
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.
return nil, fmt.Errorf("the intermediate certificate or private key is nil but not both") | |
return nil, errors.New("the intermediate certificate or private key is nil but not both") |
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.
Thank you very much for this effort, @marcosy! 🎉
* Introduce DevID Node Attestor Signed-off-by: Marcos Yedro <[email protected]>
* Introduce DevID Node Attestor Signed-off-by: Marcos Yedro <[email protected]>
Can the documentation at https://github.com/spiffe/spire/blob/main/doc/plugin_server_nodeattestor_tpm_devid.md be updated to describe the out-of-band mechanism? From reading the @marcosy comment, it seems the tool to do that is at https://github.com/HewlettPackard/devid-provisioning-tool. Maybe add a link to that? |
Pull Request check list
Affected functionality
DevID node attestor.
Description of change
This PR introduces a DevID node attestor plugin according to the proposal made in #1003 (comment)
It is based on the external plugin prototype developed by @langbeck.
In addition to the behavior described in that proposal, an optional configurable was added (check_devid_residency) to validate that the DevID resides in a TPM whose endorsement certificate is signed by a particular manufacturer CA.
This validation is generally done in the TPM provisioning phase. However, SPIRE could also perform this verification as an extra security guarantee.
This work is functional but still in progress. Documentation, unit tests and integration tests are missing as well as some tooling to facilitate the creation of DevIDs. However, I am creating this draft PR now since it may help to illustrate the proposed approach. It would be great to get some early feedback, questions and suggestions at this point.Which issue this PR fixes
Related to but not fully address #1003. There are other proposed approaches that implement node attestation using directly the TPM endorsement certificate instead of DevIDs. We may want to reserve the "tpm" plugin name for a future SPIRE implementation of those approaches.
Signed-off-by: Marcos Yedro [email protected]