Add initial tests for CreateCertificateAuthority.

This commit is contained in:
Mariano Cano 2020-10-26 19:52:08 -07:00
parent 062edcdfb4
commit 2611fc04d4
4 changed files with 198 additions and 53 deletions

View file

@ -5,25 +5,34 @@ import (
"context" "context"
"crypto/rand" "crypto/rand"
"crypto/x509" "crypto/x509"
"crypto/x509/pkix"
"encoding/asn1" "encoding/asn1"
"io" "io"
"net"
"os" "os"
"reflect" "reflect"
"testing" "testing"
"time" "time"
lroauto "cloud.google.com/go/longrunning/autogen"
privateca "cloud.google.com/go/security/privateca/apiv1beta1"
gomock "github.com/golang/mock/gomock"
"github.com/google/uuid" "github.com/google/uuid"
gax "github.com/googleapis/gax-go/v2" gax "github.com/googleapis/gax-go/v2"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/smallstep/certificates/cas/apiv1" "github.com/smallstep/certificates/cas/apiv1"
"google.golang.org/api/option"
pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" pb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1"
longrunningpb "google.golang.org/genproto/googleapis/longrunning"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/anypb"
) )
var ( var (
errTest = errors.New("test error") errTest = errors.New("test error")
testAuthorityName = "projects/test-project/locations/us-west1/certificateAuthorities/test-ca" testAuthorityName = "projects/test-project/locations/us-west1/certificateAuthorities/test-ca"
testCertificateName = "projects/test-project/locations/us-west1/certificateAuthorities/test-ca/certificates/test-certificate" testCertificateName = "projects/test-project/locations/us-west1/certificateAuthorities/test-ca/certificates/test-certificate"
testProject = "test-project"
testLocation = "us-west1"
testRootCertificate = `-----BEGIN CERTIFICATE----- testRootCertificate = `-----BEGIN CERTIFICATE-----
MIIBhjCCAS2gAwIBAgIQLbKTuXau4+t3KFbGpJJAADAKBggqhkjOPQQDAjAiMSAw MIIBhjCCAS2gAwIBAgIQLbKTuXau4+t3KFbGpJJAADAKBggqhkjOPQQDAjAiMSAw
HgYDVQQDExdHb29nbGUgQ0FTIFRlc3QgUm9vdCBDQTAeFw0yMDA5MTQyMjQ4NDla HgYDVQQDExdHb29nbGUgQ0FTIFRlc3QgUm9vdCBDQTAeFw0yMDA5MTQyMjQ4NDla
@ -72,6 +81,21 @@ ZGNhcxMkZDhkMThhNjgtNTI5Ni00YWYzLWFlNGItMmY4NzdkYTNmYmQ5MAoGCCqG
SM49BAMCA0gAMEUCIGxl+pqJ50WYWUqK2l4V1FHoXSi0Nht5kwTxFxnWZu1xAiEA SM49BAMCA0gAMEUCIGxl+pqJ50WYWUqK2l4V1FHoXSi0Nht5kwTxFxnWZu1xAiEA
zemu3bhWLFaGg3s8i+HTEhw4RqkHP74vF7AVYp88bAw= zemu3bhWLFaGg3s8i+HTEhw4RqkHP74vF7AVYp88bAw=
-----END CERTIFICATE-----` -----END CERTIFICATE-----`
testIntermediateCsr = `-----BEGIN CERTIFICATE REQUEST-----
MIIBIjCByQIBADAqMSgwJgYDVQQDEx9Hb29nbGUgQ0FTIFRlc3QgSW50ZXJtZWRp
YXRlIENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEqoztio0c4XuaaGxHFiU7
UBk3YRGTae9GtlKwyZJDk740hg6ZIoKcaXrzJT5taUpPiQLi7rP1eRui0dhl/bHo
o6A9MDsGCSqGSIb3DQEJDjEuMCwwKgYDVR0RBCMwIYIfR29vZ2xlIENBUyBUZXN0
IEludGVybWVkaWF0ZSBDQTAKBggqhkjOPQQDAgNIADBFAiEAvRKBPE32scAvsMe8
R7ecx91q58ZmeLaRdSzL7stsnJYCIEBu+vQUSTbUpKL2YQNclT9kbilips5pEMr3
ojxK6mk3
-----END CERTIFICATE REQUEST-----`
// testIntermediateKey = `-----BEGIN EC PRIVATE KEY-----
// MHcCAQEEIMM+DSPChJgcYyqDWs0eRA5BctIo+VSNqRzCTL2ARYAqoAoGCCqGSM49
// AwEHoUQDQgAEqoztio0c4XuaaGxHFiU7UBk3YRGTae9GtlKwyZJDk740hg6ZIoKc
// aXrzJT5taUpPiQLi7rP1eRui0dhl/bHoow==
// -----END EC PRIVATE KEY-----`
) )
type testClient struct { type testClient struct {
@ -146,6 +170,18 @@ func (c *testClient) GetCertificateAuthority(ctx context.Context, req *pb.GetCer
return c.certificateAuthority, c.err return c.certificateAuthority, c.err
} }
func (c *testClient) CreateCertificateAuthority(ctx context.Context, req *pb.CreateCertificateAuthorityRequest, opts ...gax.CallOption) (*privateca.CreateCertificateAuthorityOperation, error) {
return nil, errors.New("use NewMockCertificateAuthorityClient")
}
func (c *testClient) FetchCertificateAuthorityCsr(ctx context.Context, req *pb.FetchCertificateAuthorityCsrRequest, opts ...gax.CallOption) (*pb.FetchCertificateAuthorityCsrResponse, error) {
return nil, errors.New("use NewMockCertificateAuthorityClient")
}
func (c *testClient) ActivateCertificateAuthority(ctx context.Context, req *pb.ActivateCertificateAuthorityRequest, opts ...gax.CallOption) (*privateca.ActivateCertificateAuthorityOperation, error) {
return nil, errors.New("use NewMockCertificateAuthorityClient")
}
func mustParseCertificate(t *testing.T, pemCert string) *x509.Certificate { func mustParseCertificate(t *testing.T, pemCert string) *x509.Certificate {
t.Helper() t.Helper()
crt, err := parseCertificate(pemCert) crt, err := parseCertificate(pemCert)
@ -179,17 +215,34 @@ func TestNew(t *testing.T) {
}}, &CloudCAS{ }}, &CloudCAS{
client: &testClient{}, client: &testClient{},
certificateAuthority: testAuthorityName, certificateAuthority: testAuthorityName,
project: testProject,
location: testLocation,
}, false}, }, false},
{"ok with credentials", args{context.Background(), apiv1.Options{ {"ok with credentials", args{context.Background(), apiv1.Options{
CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/credentials.json", CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/credentials.json",
}}, &CloudCAS{ }}, &CloudCAS{
client: &testClient{credentialsFile: "testdata/credentials.json"}, client: &testClient{credentialsFile: "testdata/credentials.json"},
certificateAuthority: testAuthorityName, certificateAuthority: testAuthorityName,
project: testProject,
location: testLocation,
}, false},
{"ok creator", args{context.Background(), apiv1.Options{
IsCreator: true, Project: testProject, Location: testLocation,
}}, &CloudCAS{
client: &testClient{},
project: testProject,
location: testLocation,
}, false}, }, false},
{"fail certificate authority", args{context.Background(), apiv1.Options{}}, nil, true}, {"fail certificate authority", args{context.Background(), apiv1.Options{}}, nil, true},
{"fail with credentials", args{context.Background(), apiv1.Options{ {"fail with credentials", args{context.Background(), apiv1.Options{
CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/error.json", CertificateAuthority: testAuthorityName, CredentialsFile: "testdata/error.json",
}}, nil, true}, }}, nil, true},
{"fail creator project", args{context.Background(), apiv1.Options{
IsCreator: true, Project: "", Location: testLocation,
}}, nil, true},
{"fail creator location", args{context.Background(), apiv1.Options{
IsCreator: true, Project: testProject, Location: "",
}}, nil, true},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -217,6 +270,8 @@ func TestNew_register(t *testing.T) {
want := &CloudCAS{ want := &CloudCAS{
client: &testClient{credentialsFile: "testdata/credentials.json"}, client: &testClient{credentialsFile: "testdata/credentials.json"},
certificateAuthority: testAuthorityName, certificateAuthority: testAuthorityName,
project: testProject,
location: testLocation,
} }
newFn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(apiv1.CloudCAS) newFn, ok := apiv1.LoadCertificateAuthorityServiceNewFunc(apiv1.CloudCAS)
@ -675,61 +730,150 @@ func Test_getCertificateAndChain(t *testing.T) {
} }
} }
func TestCloudCAS(t *testing.T) { func TestCloudCAS_CreateCertificateAuthority(t *testing.T) {
cas, err := New(context.Background(), apiv1.Options{ must := func(a, b interface{}) interface{} {
Type: "cloudCAS", return a
CertificateAuthority: "projects/smallstep-cas-test/locations/us-west1", }
CredentialsFile: "/Users/mariano/smallstep-cas-test-8a068f3e4540.json",
}) // client, close := mockTestClient()
// defer close()
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mosCtrl := gomock.NewController(t)
defer mosCtrl.Finish()
m := NewMockCertificateAuthorityClient(ctrl)
mos := NewMockOperationsServer(mosCtrl)
// Create operation server
lis, err := net.Listen("tcp", "localhost:0")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
// resp, err := cas.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{ srv := grpc.NewServer()
// Type: apiv1.RootCA, longrunningpb.RegisterOperationsServer(srv, mos)
// Template: &x509.Certificate{
// Subject: pkix.Name{ go srv.Serve(lis)
// CommonName: "Test Mariano Root CA", defer srv.Stop()
// },
// BasicConstraintsValid: true, // Create fake privateca client
// IsCA: true, conn, err := grpc.Dial(lis.Addr().String(), grpc.WithInsecure())
// MaxPathLen: 1, if err != nil {
// MaxPathLenZero: false, t.Fatal(err)
// KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
// },
// Lifetime: time.Duration(30 * 24 * time.Hour),
// Project: "smallstep-cas-test",
// Location: "us-west1",
// })
// if err != nil {
// t.Fatal(err)
// }
// debug(resp)
resp := &apiv1.CreateCertificateAuthorityResponse{
Name: "projects/smallstep-cas-test/locations/us-west1/certificateAuthorities/9a593da4-61af-4426-a2f8-0650373b9c8e",
} }
resp, err = cas.CreateCertificateAuthority(&apiv1.CreateCertificateAuthorityRequest{ client, err := lroauto.NewOperationsClient(context.Background(), option.WithGRPCConn(conn))
Type: apiv1.IntermediateCA, if err != nil {
Template: &x509.Certificate{ t.Fatal(err)
Subject: pkix.Name{ }
Country: []string{"US"}, fake := &privateca.CertificateAuthorityClient{
CommonName: "Test Mariano Intermediate CA", LROClient: client,
}, }
BasicConstraintsValid: true,
IsCA: true, // Configure mocks
MaxPathLen: 0, any := gomock.Any()
MaxPathLenZero: true,
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, // ok root
m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil)
mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{
Name: "CreateCertificateAuthority",
Done: true,
Result: &longrunningpb.Operation_Response{
Response: must(anypb.New(&pb.CertificateAuthority{
Name: testAuthorityName,
PemCaCertificates: []string{testRootCertificate},
})).(*anypb.Any),
}, },
Lifetime: time.Duration(24 * time.Hour), }, nil)
Parent: resp,
Project: "smallstep-cas-test", // ok intermediate
Location: "us-west1", m.EXPECT().CreateCertificateAuthority(any, any).Return(fake.CreateCertificateAuthorityOperation("CreateCertificateAuthority"), nil)
}) mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{
if err != nil { Name: "CreateCertificateAuthority",
t.Fatal(err) Done: true,
Result: &longrunningpb.Operation_Response{
Response: must(anypb.New(&pb.CertificateAuthority{
Name: testAuthorityName,
})).(*anypb.Any),
},
}, nil)
m.EXPECT().FetchCertificateAuthorityCsr(any, any).Return(&pb.FetchCertificateAuthorityCsrResponse{
PemCsr: testIntermediateCsr,
}, nil)
m.EXPECT().CreateCertificate(any, any).Return(&pb.Certificate{
PemCertificate: testIntermediateCertificate,
PemCertificateChain: []string{testRootCertificate},
}, nil)
m.EXPECT().ActivateCertificateAuthority(any, any).Return(fake.ActivateCertificateAuthorityOperation("ActivateCertificateAuthority"), nil)
mos.EXPECT().GetOperation(any, any).Return(&longrunningpb.Operation{
Name: "ActivateCertificateAuthority",
Done: true,
Result: &longrunningpb.Operation_Response{
Response: must(anypb.New(&pb.CertificateAuthority{
Name: testAuthorityName,
PemCaCertificates: []string{testIntermediateCertificate, testRootCertificate},
})).(*anypb.Any),
},
}, nil)
rootCrt := mustParseCertificate(t, testRootCertificate)
intCrt := mustParseCertificate(t, testIntermediateCertificate)
type fields struct {
client CertificateAuthorityClient
certificateAuthority string
project string
location string
}
type args struct {
req *apiv1.CreateCertificateAuthorityRequest
}
tests := []struct {
name string
fields fields
args args
want *apiv1.CreateCertificateAuthorityResponse
wantErr bool
}{
{"ok root", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{
Type: apiv1.RootCA,
Template: mustParseCertificate(t, testRootCertificate),
Lifetime: 24 * time.Hour,
}}, &apiv1.CreateCertificateAuthorityResponse{
Name: testAuthorityName,
Certificate: rootCrt,
}, false},
{"ok intermediate", fields{m, "", testProject, testLocation}, args{&apiv1.CreateCertificateAuthorityRequest{
Type: apiv1.IntermediateCA,
Template: mustParseCertificate(t, testIntermediateCertificate),
Lifetime: 24 * time.Hour,
Parent: &apiv1.CreateCertificateAuthorityResponse{
Name: testAuthorityName,
Certificate: rootCrt,
},
}}, &apiv1.CreateCertificateAuthorityResponse{
Name: testAuthorityName,
Certificate: intCrt,
CertificateChain: []*x509.Certificate{rootCrt},
}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &CloudCAS{
client: tt.fields.client,
certificateAuthority: tt.fields.certificateAuthority,
project: tt.fields.project,
location: tt.fields.location,
}
got, err := c.CreateCertificateAuthority(tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("CloudCAS.CreateCertificateAuthority() error = %+v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("CloudCAS.CreateCertificateAuthority() = %v, want %v", got, tt.want)
}
})
} }
// debug(resp)
t.Error("foo")
} }

View file

@ -173,9 +173,7 @@ func (c *SoftCAS) CreateCertificateAuthority(req *apiv1.CreateCertificateAuthori
var chain []*x509.Certificate var chain []*x509.Certificate
if req.Parent != nil { if req.Parent != nil {
chain = append(chain, req.Parent.Certificate) chain = append(chain, req.Parent.Certificate)
for _, crt := range req.Parent.CertificateChain { chain = append(chain, req.Parent.CertificateChain...)
chain = append(chain, crt)
}
} }
return &apiv1.CreateCertificateAuthorityResponse{ return &apiv1.CreateCertificateAuthorityResponse{

1
go.mod
View file

@ -8,6 +8,7 @@ require (
github.com/aws/aws-sdk-go v1.30.29 github.com/aws/aws-sdk-go v1.30.29
github.com/go-chi/chi v4.0.2+incompatible github.com/go-chi/chi v4.0.2+incompatible
github.com/go-piv/piv-go v1.6.0 github.com/go-piv/piv-go v1.6.0
github.com/golang/mock v1.4.4
github.com/google/uuid v1.1.2 github.com/google/uuid v1.1.2
github.com/googleapis/gax-go/v2 v2.0.5 github.com/googleapis/gax-go/v2 v2.0.5
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect

2
go.sum
View file

@ -116,6 +116,7 @@ github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFU
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -134,6 +135,7 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=