Skip to content

Commit

Permalink
Reworks pki infastructure in Juju for a structure.
Browse files Browse the repository at this point in the history
- Removes existing code in Juju for cert management for a new pki
  package that prefers structured interfaces.

- Key profiles for signing operations can be changed through a constant

- PEM encoding for certificates has been upgrade to PKCS8 over PKCS1.
  This change will allow us to move to ECDSA signed certificates.

- Prefers the use of multiple certificates to solve problems insteaf of
  a single controller certificate.

- HTTP server tls config updated to support multiple certificates
  through the use of SNI.

- APIServerCertUpdater worker changed to just offer a single Authority
  pki now. It does not have to transform certificates to tls.Certificate
  anymore for the http server.

-- List of TODO's that can be performed a later date.

- TODO upgrade to ECDSA certificates once we are using mongodb compiled
  with latest openssl across K8's platoforms.

- TODO upgrade to use of tls.Certificate Supports* when we move to go
  1.14

- TODO investigate the use of headers in PKCS8 encoding to support writing
  leaf groups out with certificates in pem format.
  • Loading branch information
tlm committed Apr 9, 2020
1 parent b7b7623 commit b406e62
Show file tree
Hide file tree
Showing 66 changed files with 2,476 additions and 1,419 deletions.
29 changes: 29 additions & 0 deletions api/caasadmission/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2018 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package caasadmission

import (
"github.com/juju/errors"

"github.com/juju/juju/api/base"
"github.com/juju/juju/api/common"
)

// Client provides access to controller config
type Client struct {
facade base.FacadeCaller
*common.ControllerConfigAPI
}

func NewClient(caller base.APICaller) (*Client, error) {
_, isModel := caller.ModelTag()
if !isModel {
return nil, errors.New("expected model specific API connection")
}
facadeCaller := base.NewFacadeCaller(caller, "CAASAdmission")
return &Client{
facade: facadeCaller,
ControllerConfigAPI: common.NewControllerConfig(facadeCaller),
}, nil
}
16 changes: 11 additions & 5 deletions api/certpool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ import (
"io/ioutil"
"path/filepath"
"strings"
"time"

"github.com/juju/loggo"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"

"github.com/juju/juju/api"
"github.com/juju/juju/cert"
"github.com/juju/juju/pki"
"github.com/juju/juju/testing"
)

Expand Down Expand Up @@ -126,10 +125,17 @@ func (s *certPoolSuite) TestCreateCertPoolLogsBadCerts(c *gc.C) {
}

func (s *certPoolSuite) addCert(c *gc.C, filename string) {
expiry := time.Now().UTC().AddDate(10, 0, 0)
pem, _, err := cert.NewCA("random model name", "1", expiry)
signer, err := pki.DefaultKeyProfile()
c.Assert(err, jc.ErrorIsNil)
err = ioutil.WriteFile(filename, []byte(pem), 0644)

caCert, err := pki.NewCA("random model name", signer)
c.Assert(err, jc.ErrorIsNil)

caCertPem, err := pki.CertificateToPemString(pki.DefaultPemHeaders, caCert)
c.Assert(err, jc.ErrorIsNil)

c.Assert(err, jc.ErrorIsNil)
err = ioutil.WriteFile(filename, []byte(caCertPem), 0644)
c.Assert(err, jc.ErrorIsNil)
}

Expand Down
1 change: 1 addition & 0 deletions api/facadeversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ var facadeVersions = map[string]int{
"Block": 2,
"Bundle": 4,
"CAASAgent": 1,
"CAASAdmission": 1,
"CAASFirewaller": 1,
"CAASOperator": 1,
"CAASOperatorProvisioner": 1,
Expand Down
2 changes: 2 additions & 0 deletions apiserver/allfacades.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/juju/juju/apiserver/common"
"github.com/juju/juju/apiserver/facade"
"github.com/juju/juju/apiserver/facades/agent/agent"
"github.com/juju/juju/apiserver/facades/agent/caasadmission"
"github.com/juju/juju/apiserver/facades/agent/caasagent"
"github.com/juju/juju/apiserver/facades/agent/caasoperator"
"github.com/juju/juju/apiserver/facades/agent/credentialvalidator"
Expand Down Expand Up @@ -181,6 +182,7 @@ func AllFacades() *facade.Registry {
// Move these to the correct place above once the feature flag disappears.
reg("CAASFirewaller", 1, caasfirewaller.NewStateFacade)
reg("CAASOperator", 1, caasoperator.NewStateFacade)
reg("CAASAdmission", 1, caasadmission.NewStateFacade)
reg("CAASAgent", 1, caasagent.NewStateFacade)
reg("CAASOperatorProvisioner", 1, caasoperatorprovisioner.NewStateCAASOperatorProvisionerAPI)
reg("CAASOperatorUpgrader", 1, caasoperatorupgrader.NewStateCAASOperatorUpgraderAPI)
Expand Down
26 changes: 26 additions & 0 deletions apiserver/facades/agent/caasadmission/caasadmission.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright 2018 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package caasadmission

import (
"github.com/juju/juju/apiserver/common"
"github.com/juju/juju/apiserver/facade"
)

type Facade struct {
auth facade.Authorizer
*common.ControllerConfigAPI
}

func NewStateFacade(ctx facade.Context) (*Facade, error) {
authorizer := ctx.Auth()
if !authorizer.AuthMachineAgent() {
return nil, common.ErrPerm
}

return &Facade{
auth: authorizer,
ControllerConfigAPI: common.NewStateControllerConfig(ctx.State()),
}, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ import (
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/caas"
"github.com/juju/juju/caas/kubernetes/provider"
"github.com/juju/juju/cert"
"github.com/juju/juju/cloudconfig/podcfg"
"github.com/juju/juju/environs/config"
"github.com/juju/juju/environs/tags"
"github.com/juju/juju/pki"
"github.com/juju/juju/state"
"github.com/juju/juju/state/stateenvirons"
"github.com/juju/juju/state/watcher"
Expand Down Expand Up @@ -201,6 +201,12 @@ func (a *API) IssueOperatorCertificate(args params.Entities) (params.IssueOperat
return params.IssueOperatorCertificateResults{}, errors.Trace(err)
}

authority, err := pki.NewDefaultAuthorityPemCAKey([]byte(caCert),
[]byte(si.CAPrivateKey))
if err != nil {
return params.IssueOperatorCertificateResults{}, errors.Trace(err)
}

res := params.IssueOperatorCertificateResults{
Results: make([]params.IssueOperatorCertificateResult, len(args.Entities)),
}
Expand All @@ -213,20 +219,29 @@ func (a *API) IssueOperatorCertificate(args params.Entities) (params.IssueOperat
continue
}

hostnames := []string{
appTag.Name,
leaf, err := authority.LeafRequestForGroup(appTag.Name).
AddDNSNames(appTag.Name).
Commit()

if err != nil {
res.Results[i] = params.IssueOperatorCertificateResult{
Error: common.ServerError(err),
}
continue
}
cert, privateKey, err := cert.NewDefaultServer(caCert, si.CAPrivateKey, hostnames)

cert, privateKey, err := leaf.ToPemParts()
if err != nil {
res.Results[i] = params.IssueOperatorCertificateResult{
Error: common.ServerError(err),
}
continue
}

res.Results[i] = params.IssueOperatorCertificateResult{
CACert: caCert,
Cert: cert,
PrivateKey: privateKey,
CACert: string(caCert),
Cert: string(cert),
PrivateKey: string(privateKey),
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,7 @@
package caasoperatorprovisioner_test

import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"

"github.com/juju/errors"
jc "github.com/juju/testing/checkers"
Expand All @@ -23,6 +18,7 @@ import (
"github.com/juju/juju/apiserver/params"
apiservertesting "github.com/juju/juju/apiserver/testing"
"github.com/juju/juju/core/life"
"github.com/juju/juju/pki"
"github.com/juju/juju/state"
coretesting "github.com/juju/juju/testing"
jujuversion "github.com/juju/juju/version"
Expand Down Expand Up @@ -256,26 +252,19 @@ func (s *CAASProvisionerSuite) TestIssueOperatorCertificate(c *gc.C) {
c.Assert(res.Results, gc.HasLen, 1)
certInfo := res.Results[0]
c.Assert(certInfo.Error, gc.IsNil)
certBlock, rem := pem.Decode([]byte(certInfo.Cert))
c.Assert(rem, gc.HasLen, 0)
keyBlock, rem := pem.Decode([]byte(certInfo.PrivateKey))
c.Assert(rem, gc.HasLen, 0)
cert, err := x509.ParseCertificate(certBlock.Bytes)

certs, signers, err := pki.UnmarshalPemData(append([]byte(certInfo.Cert),
[]byte(certInfo.PrivateKey)...))
c.Assert(err, jc.ErrorIsNil)
c.Assert(len(signers), gc.Equals, 1)
c.Assert(len(certs), gc.Equals, 1)

roots := x509.NewCertPool()
ok := roots.AppendCertsFromPEM([]byte(certInfo.CACert))
c.Assert(ok, jc.IsTrue)
_, err = cert.Verify(x509.VerifyOptions{
_, err = certs[0].Verify(x509.VerifyOptions{
DNSName: "appname",
Roots: roots,
})
c.Assert(err, jc.ErrorIsNil)
key, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes)
c.Assert(err, jc.ErrorIsNil)
toSign := []byte("hello juju")
hash := sha256.Sum256(toSign)
sig, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, hash[:])
c.Assert(err, jc.ErrorIsNil)
err = cert.CheckSignature(x509.SHA256WithRSA, toSign, sig)
c.Assert(err, jc.ErrorIsNil)
}
137 changes: 137 additions & 0 deletions apiserver/facades/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -5328,6 +5328,143 @@
}
}
},
{
"Name": "CAASAdmission",
"Version": 1,
"Schema": {
"type": "object",
"properties": {
"ControllerAPIInfoForModels": {
"type": "object",
"properties": {
"Params": {
"$ref": "#/definitions/Entities"
},
"Result": {
"$ref": "#/definitions/ControllerAPIInfoResults"
}
}
},
"ControllerConfig": {
"type": "object",
"properties": {
"Result": {
"$ref": "#/definitions/ControllerConfigResult"
}
}
}
},
"definitions": {
"ControllerAPIInfoResult": {
"type": "object",
"properties": {
"addresses": {
"type": "array",
"items": {
"type": "string"
}
},
"cacert": {
"type": "string"
},
"error": {
"$ref": "#/definitions/Error"
}
},
"additionalProperties": false,
"required": [
"addresses",
"cacert"
]
},
"ControllerAPIInfoResults": {
"type": "object",
"properties": {
"results": {
"type": "array",
"items": {
"$ref": "#/definitions/ControllerAPIInfoResult"
}
}
},
"additionalProperties": false,
"required": [
"results"
]
},
"ControllerConfigResult": {
"type": "object",
"properties": {
"config": {
"type": "object",
"patternProperties": {
".*": {
"type": "object",
"additionalProperties": true
}
}
}
},
"additionalProperties": false,
"required": [
"config"
]
},
"Entities": {
"type": "object",
"properties": {
"entities": {
"type": "array",
"items": {
"$ref": "#/definitions/Entity"
}
}
},
"additionalProperties": false,
"required": [
"entities"
]
},
"Entity": {
"type": "object",
"properties": {
"tag": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"tag"
]
},
"Error": {
"type": "object",
"properties": {
"code": {
"type": "string"
},
"info": {
"type": "object",
"patternProperties": {
".*": {
"type": "object",
"additionalProperties": true
}
}
},
"message": {
"type": "string"
}
},
"additionalProperties": false,
"required": [
"message",
"code"
]
}
}
}
},
{
"Name": "CAASAgent",
"Version": 1,
Expand Down
1 change: 1 addition & 0 deletions apiserver/restrict_caasmodel.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ var commonModelFacadeNames = set.NewStrings(
// caasModelFacadeNames lists facades that are only used with CAAS
// models.
var caasModelFacadeNames = set.NewStrings(
"CAASAdmission",
"CAASAgent",
"CAASFirewaller",
"CAASOperator",
Expand Down
Loading

0 comments on commit b406e62

Please sign in to comment.