Skip to content

Commit

Permalink
docs: add site about certificates
Browse files Browse the repository at this point in the history
  • Loading branch information
3u13r committed Apr 18, 2024
1 parent ef472e4 commit e8d219d
Show file tree
Hide file tree
Showing 11 changed files with 124 additions and 61 deletions.
2 changes: 1 addition & 1 deletion cli/cmd/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func NewSetCmd() *cobra.Command {
reference values embedded into the CLI.
After the connection is established, the manifest is set. The Coordinator
will re-generate the mesh root certificate and accept new workloads to
will re-generate the mesh CA certificate and accept new workloads to
issuer certificates.
`,
RunE: runSet,
Expand Down
6 changes: 3 additions & 3 deletions coordinator/meshapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,12 @@ func (i *meshAPIServer) NewMeshCert(_ context.Context, req *meshapi.NewMeshCertR
"getting certificate with public key hash %q: %v", req.PeerPublicKeyHash, err)
}

meshCACert := i.caChainGetter.GetMeshRootCert()
intermCert := i.caChainGetter.GetIntermCert()
meshCACert := i.caChainGetter.GetMeshCACert()
intermCACert := i.caChainGetter.GetIntermCACert()

return &meshapi.NewMeshCertResponse{
MeshCACert: meshCACert,
CertChain: append(cert, intermCert...),
CertChain: append(cert, intermCACert...),
RootCACert: i.caChainGetter.GetCoordinatorRootCert(),
}, nil
}
8 changes: 4 additions & 4 deletions coordinator/userapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ func (s *userAPIServer) SetManifest(ctx context.Context, req *userapi.SetManifes

resp := &userapi.SetManifestResponse{
CoordinatorRoot: s.caChainGetter.GetCoordinatorRootCert(),
MeshRoot: s.caChainGetter.GetMeshRootCert(),
MeshRoot: s.caChainGetter.GetMeshCACert(),
}

s.logger.Info("SetManifest succeeded")
Expand Down Expand Up @@ -134,7 +134,7 @@ func (s *userAPIServer) GetManifests(_ context.Context, _ *userapi.GetManifestsR
Manifests: manifestBytes,
Policies: policySliceToBytesSlice(policies),
CoordinatorRoot: s.caChainGetter.GetCoordinatorRootCert(),
MeshRoot: s.caChainGetter.GetMeshRootCert(),
MeshRoot: s.caChainGetter.GetMeshCACert(),
}

s.logger.Info("GetManifest succeeded")
Expand Down Expand Up @@ -211,8 +211,8 @@ func manifestSliceToBytesSlice(s []*manifest.Manifest) ([][]byte, error) {

type certChainGetter interface {
GetCoordinatorRootCert() []byte
GetMeshRootCert() []byte
GetIntermCert() []byte
GetMeshCACert() []byte
GetIntermCACert() []byte
}

type manifestSetGetter interface {
Expand Down
4 changes: 2 additions & 2 deletions coordinator/userapi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,8 @@ func (s *stubManifestSetGetter) LatestManifest() (*manifest.Manifest, error) {
type stubCertChainGetter struct{}

func (s *stubCertChainGetter) GetCoordinatorRootCert() []byte { return []byte("root") }
func (s *stubCertChainGetter) GetMeshRootCert() []byte { return []byte("mesh") }
func (s *stubCertChainGetter) GetIntermCert() []byte { return []byte("inter") }
func (s *stubCertChainGetter) GetMeshCACert() []byte { return []byte("mesh") }
func (s *stubCertChainGetter) GetIntermCACert() []byte { return []byte("inter") }

func rpcContext(key *ecdsa.PrivateKey) context.Context {
var peerCertificates []*x509.Certificate
Expand Down
4 changes: 4 additions & 0 deletions docs/docs/_media/contrast_pki.drawio.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
69 changes: 69 additions & 0 deletions docs/docs/architecture/certificates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Certificate authority

The Coordinator acts as a certificate authority (CA) for the workloads
defined in the manifest.
After a workload pod's attestation has been verified by the Coordinator,
it receives a mesh certificate and the mesh CA certificate.
The mesh certificate can be used for example in a TLS connection as the server or
client certificate to proof to the other party that the workload has been
verified by the Coordinator. The other party can verify the mesh certificate
with the mesh CA certificate. While the certificates can be used by the workload
developer in different ways, they're automatically used in Contrast's service
mesh to establish mTLS connections between workloads in the same deployment.

## Public key infrastructure

The Coordinator establishes a public key infrastructure (PKI) for all workloads
contained in the manifest. The Coordinator holds three certificates: the root CA
certificate, the intermediate certificate and the mesh CA certificate.
The root CA certificate is a long-lasting certificate and it's private key signs
the intermediate certificate. The intermediate certificate and the mesh CA
certificate share the same private key. This intermediate private key is used
to sign the mesh certificates. Moreover, the intermediate private key and
therefore the intermediate certificate and the mesh CA certificate are
rotated when setting a new manifest.

![PKI certificate chain](../_media/contrast_pki.drawio.svg)

## Certificate rotation

Depending on the configuration of the first manifest, it allows the workload
owner to update the manifest and therefore the deployment. To protect against
the introduction of a malicious container, every time the manifest is updated,
the Coordinator rotates the intermediate private key and therefore the
intermediate CA certificate and mesh CA certificate. If the user decides to not
trust the workload owner, they use the mesh CA certificate from the time they
verified the Coordinator and the manifest. This ensures that the user only
connects to workloads defined the manifest they verified since only those
workloads' certificates are singed with this intermediate private key.

Similarly, the service mesh also uses the mesh CA certificate from the time
where the workload was started so the workload doesn't assume that an endpoint
is trusted just because the endpoint has a certificate created by the
coordinator.

## Usage of the different certificates

- The **root CA certificate** is returned when verifying the Coordinator. It can
be used by the data owner to verify the mesh certificates of the workloads.
This should only be used, if the data owner trusts all future updates to the
manifest and workloads. This is for instance the case when the workload owner is
the same person as the data owner.
- The **mesh CA certificate** is returned when verifying the Coordinator. It can
be used by the data owner to verify the mesh certificates of the workloads.
This certificate is bound to the manifest set when the Coordinator was verified.
If the manifest is updated, new workloads will receive mesh certificates that
are _not_ signed by the already retrieved mesh CA certificate certificate.
Instead, the Coordinator with the new manifest needs to be verified to retrieve
the new mesh CA certificate. This certificate is also used by the service mesh
to verify the mesh certificates.
- The **intermediate CA certificate** links the root CA certificate to the
mesh certificate so that the mesh certificate can be verified with the root CA
certificate. It's part of the certificate chain handed out by
endpoints in the service mesh.
- The **mesh certificate** is part of the certificate chain handed out by
endpoints in the service mesh. During the startup of a pod, the Initializer
requests a certificate from the Coordinator. If the Coordinator successfully
verifies the workload, it sends back this mesh certificate. The mesh certificate
contains X.509 extensions with information from the workloads attestation
document.
11 changes: 5 additions & 6 deletions docs/docs/examples/emojivoto.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
**This tutorial guides you through deploying [emojivoto](https://github.com/BuoyantIO/emojivoto) as a
confidential Contrast deployment and validating the deployment from a voters perspective.**


Emojivoto is an example app allowing users to vote for different emojis and view votes
on a leader board. It has a microservice architecture consisting of a
web frontend (`web`), a gRPC backend for listing available emojis (`emoji`), and a backend for
Expand Down Expand Up @@ -120,8 +119,8 @@ contrast verify -c "${coordinator}:1313"
The CLI will attest the Coordinator using embedded reference values. If the command succeeds,
the Coordinator deployment was successfully verified to be running in the expected Confidential
Computing environment with the expected code version. The Coordinator will then return its
configuration over the established TLS channel. The CLI will store this information, namely the root
certificate of the mesh (`mesh-root.pem`) and the history of manifests, into the `verify/` directory.
configuration over the established TLS channel. The CLI will store this information, namely the CA
certificate of the mesh (`mesh-ca-cert.pem`) and the history of manifests, into the `verify/` directory.
In addition, the policies referenced in the manifest history are also written into the same directory.

### Manifest history and artifact audit
Expand Down Expand Up @@ -155,7 +154,7 @@ openssl s_client -CAfile verify/mesh-root.pem -verify_return_error -connect ${fr
By default, mesh certificates are issued with a wildcard DNS entry. The web frontend is accessed
via load balancer IP in this demo. Tools like curl check the certificate for IP entries in the SAN field.
Validation fails since the certificate doesn't contain any IP entries as a subject alternative name (SAN).
For example, a connection attempt using the curl and the mesh root certificate with throw the following error:
For example, a connection attempt using the curl and the mesh CA certificate with throw the following error:

```sh
$ curl --cacert ./verify/mesh-root.pem "https://${frontendIP}:443"
Expand Down Expand Up @@ -187,11 +186,11 @@ Next, set the changed manifest at the coordinator with:
contrast set -c "${coordinator}:1313" deployment/
```

The Contrast Coordinator will rotate the mesh root certificate on the manifest update. Workload certificates issued
The Contrast Coordinator will rotate the mesh CA certificate on the manifest update. Workload certificates issued
after the manifest are thus issued by another certificate authority and services receiving the new CA certificate chain
won't trust parts of the deployment that got their certificate issued before the update. This way, Contrast ensures
that parts of the deployment that received a security update won't be infected by parts of the deployment at an older
patch level that may have been compromised. The `mesh-root.pem` is updated with the new CA certificate chain.
patch level that may have been compromised. The `mesh-CA-cert.pem` is updated with the new CA certificate chain.

### Rolling out the update

Expand Down
15 changes: 3 additions & 12 deletions docs/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,18 +161,9 @@ const sidebars = {
]
},
{
type: 'category',
label: 'Certificates and Identities',
link: {
type: 'generated-index',
},
items: [
{
type: 'doc',
label: 'PKI',
id: 'architecture/certificates-and-identities/pki',
},
]
type: 'doc',
label: 'Certificate authority',
id: 'architecture/certificates',
},
{
type: 'category',
Expand Down
40 changes: 20 additions & 20 deletions internal/ca/ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import (

// CA is a cross-signing certificate authority.
type CA struct {
rootPrivKey *ecdsa.PrivateKey
rootCert *x509.Certificate
rootPEM []byte
rootCAPrivKey *ecdsa.PrivateKey
rootCACert *x509.Certificate
rootCAPEM []byte

// The intermPrivKey is used for both the intermediate and meshCA certificates.
// This implements cross-signing for the leaf certificates.
Expand All @@ -30,8 +30,8 @@ type CA struct {
intermMux sync.RWMutex
intermPrivKey *ecdsa.PrivateKey

intermCert *x509.Certificate
intermPEM []byte
intermCACert *x509.Certificate
intermCAPEM []byte

meshCACert *x509.Certificate
meshCAPEM []byte
Expand Down Expand Up @@ -61,9 +61,9 @@ func New() (*CA, error) {
}

ca := CA{
rootPrivKey: rootPrivKey,
rootCert: root,
rootPEM: rootPEM,
rootCAPrivKey: rootPrivKey,
rootCACert: root,
rootCAPEM: rootPEM,
}
if err := ca.RotateIntermCerts(); err != nil {
return nil, fmt.Errorf("rotating intermediate certificates: %w", err)
Expand Down Expand Up @@ -110,19 +110,19 @@ func (c *CA) NewAttestedMeshCert(names []string, extensions []pkix.Extension, su
return certPEM, nil
}

// RotateIntermCerts rotates the intermediate certificates.
// RotateIntermCerts rotates the intermediate CA and mesh CA certificate.
// All existing mesh certificates will remain valid under the rootCA but
// not under the new intermediate certificate.
// To distribute the new intermediate certificate, all workloads should
// be restarted.
// not under the new intermediate CA and mesh CA certificates.
// To distribute the new intermediate CA and mesh CA certificates, all workloads
// should be restarted.
func (c *CA) RotateIntermCerts() error {
c.intermMux.Lock()
defer c.intermMux.Unlock()

now := time.Now()
notBefore := now.Add(-time.Hour)
notAfter := now.AddDate(10, 0, 0)
c.intermCert = &x509.Certificate{
c.intermCACert = &x509.Certificate{
Subject: pkix.Name{CommonName: "system:coordinator:meshCA"},
NotBefore: notBefore,
NotAfter: notAfter,
Expand All @@ -135,7 +135,7 @@ func (c *CA) RotateIntermCerts() error {
if err != nil {
return fmt.Errorf("generating intermediate private key: %w", err)
}
c.intermPEM, err = createCert(c.intermCert, c.rootCert, &c.intermPrivKey.PublicKey, c.rootPrivKey)
c.intermCAPEM, err = createCert(c.intermCACert, c.rootCACert, &c.intermPrivKey.PublicKey, c.rootCAPrivKey)
if err != nil {
return fmt.Errorf("creating intermediate certificate: %w", err)
}
Expand All @@ -158,16 +158,16 @@ func (c *CA) RotateIntermCerts() error {

// GetCoordinatorRootCert returns the root certificate of the CA in PEM format.
func (c *CA) GetCoordinatorRootCert() []byte {
return c.rootPEM
return c.rootCAPEM
}

// GetIntermCert returns the intermediate certificate of the CA in PEM format.
func (c *CA) GetIntermCert() []byte {
return c.intermPEM
// GetIntermCACert returns the intermediate certificate of the CA in PEM format.
func (c *CA) GetIntermCACert() []byte {
return c.intermCAPEM
}

// GetMeshRootCert returns the mesh root certificate of the CA in PEM format.
func (c *CA) GetMeshRootCert() []byte {
// GetMeshCACert returns the mesh CA certificate of the CA in PEM format.
func (c *CA) GetMeshCACert() []byte {
return c.meshCAPEM
}

Expand Down
26 changes: 13 additions & 13 deletions internal/ca/ca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ func TestNewCA(t *testing.T) {
ca, err := New()
require.NoError(err)
assert.NotNil(ca)
assert.NotNil(ca.rootPrivKey)
assert.NotNil(ca.rootCert)
assert.NotNil(ca.rootPEM)
assert.NotNil(ca.rootCAPrivKey)
assert.NotNil(ca.rootCACert)
assert.NotNil(ca.rootCAPEM)
assert.NotNil(ca.intermPrivKey)
assert.NotNil(ca.intermCert)
assert.NotNil(ca.intermPEM)
assert.NotNil(ca.intermCACert)
assert.NotNil(ca.intermCAPEM)

root := x509.NewCertPool()
ok := root.AppendCertsFromPEM(ca.rootPEM)
ok := root.AppendCertsFromPEM(ca.rootCAPEM)
assert.True(ok)

cert := parsePEMCertificate(t, ca.intermPEM)
cert := parsePEMCertificate(t, ca.intermCAPEM)

opts := x509.VerifyOptions{Roots: root}

Expand Down Expand Up @@ -158,15 +158,15 @@ func TestRotateIntermCerts(t *testing.T) {
ca, err := New()
require.NoError(err)

oldIntermCert := ca.intermCert
oldintermPEM := ca.intermPEM
oldIntermCert := ca.intermCACert
oldintermPEM := ca.intermCAPEM
oldMeshCACert := ca.meshCACert
oldMeshCAPEM := ca.meshCAPEM

err = ca.RotateIntermCerts()
assert.NoError(err)
assert.NotEqual(oldIntermCert, ca.intermCert)
assert.NotEqual(oldintermPEM, ca.intermPEM)
assert.NotEqual(oldIntermCert, ca.intermCACert)
assert.NotEqual(oldintermPEM, ca.intermCAPEM)
assert.NotEqual(oldMeshCACert, ca.meshCACert)
assert.NotEqual(oldMeshCAPEM, ca.meshCAPEM)
}
Expand All @@ -181,11 +181,11 @@ func TestCAConcurrent(t *testing.T) {
wg := sync.WaitGroup{}
getIntermCert := func() {
defer wg.Done()
assert.NotEmpty(ca.GetIntermCert())
assert.NotEmpty(ca.GetIntermCACert())
}
getMeshCACert := func() {
defer wg.Done()
assert.NotEmpty(ca.GetMeshRootCert())
assert.NotEmpty(ca.GetMeshCACert())
}
getRootCACert := func() {
defer wg.Done()
Expand Down

0 comments on commit e8d219d

Please sign in to comment.