AWS
Dependencies:
baseca
Configuration:
baseca
Database:
baseca
gRPC Server:
- Option 1: Run baseca as One-Off Execution
- Option 2: Build and Run baseca as Container
- Option 3: Compile baseca as Executable (amd64)
Signing
x.509 Certificate:
Each organization will have different Public Key Infrastructure topologies depending on its needs; for your PKI to be compatible with baseca
(a) Certificate Authorities must be AWS Private CA and (b) there must be a minimum PathLen depending on where baseca
issues the Subordinate CA from. Designing a Public Key Infrastructure is out of scope of this document, but we will take a look at topologies that baseca
is compatible with below:
-
Option 1: Root CA Per Environment (Self-Managed) → Intermediate CA (AWS): Minimum PathLen2 on Root CA, PathLen1 on Intermediate CA (Highest Complexity, Recommended)
-
Option 2: Root CA (Self-Managed) → Intermediate CA (AWS): Minimum PathLen2 on Root CA, PathLen1 on Intermediate CA (Higher Complexity, Recommended)
-
Option 3: Root CA (AWS) → Intermediate CA (AWS): Minimum PathLen2 on Root CA, PathLen1 on Intermediate CA (Lower Complexity, Recommended)
-
Option 4: Root CA (AWS) → No AWS Intermediate CA: Minimum PathLen1 on Root CA (Not Recommended)
Note: If this approach is used onle a single environment can be supported.
brew install tfenv
tfenv install 1.4.2
tfenv use 1.4.2
baseca
Infrastructure Documentation
NOTE: Private CA(s) in acm_pca_arns
must already exist within your infrastructure; refer to the Public Key Infrastructure section if you need to design and deploy a Public Key Infrastructure.
DISCLAIMER: DO NOT
use Private CA(s) that are used within your organization's PRODUCTION
environment for this GETTING_STARTED.md
document, this is meant to build a local development environment. For production deployments please refer to PRODUCTION_DEPLOYMENT.md
.
# /path/to/baseca/terraform/development/baseca.tf
module "baseca" {
source = "./baseca"
service = "baseca"
environment = "development"
region = "us-east-1"
key_spec = "RSA_4096"
bucket = "baseca-firehose-example"
}
cd /path/to/baseca/terraform/development
terraform init
terraform apply
These outputs from Terraform will be utilized within the baseca config.primary.local.sandbox.yml
configuration file.
terraform output
# Example Output
kinesis_firehose_stream = "baseca-development"
kms_key_id = "xxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
Create and update the configuration config/config.primary.local.sandbox.yml
file using the outputs created from the Terraform infrastructure; an example configuration can be seen within CONFIGURATION.md.
# Update config.primary.local.sandbox.yml
firehose:
stream: baseca-development
kms:
key_id: xxxxxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # kms_key_id
Variable | Description | Update |
---|---|---|
grpc_server_address |
gRPC Server Address and Port | No |
ocsp_server |
Custom OCSP Server URL | Remove for Local Development |
database |
baseca RDS Database | No |
redis |
baseca Elasticache Redis Cluster | Remove for Local Development |
domains |
List of Valid Domains for baseca x.509 Certificates |
Yes |
firehose |
baseca Kinesis Data Firehose | Yes |
kms |
baseca Customer Managed KMS Key | Yes |
acm_pca |
AWS Private Certificate Authorities | Yes |
secrets_manager |
AWS Secrets Manager | Remove for Local Development |
subordinate_ca_metadata |
baseca Subordinate CA Attributes | Optional |
certificate_authority |
Environment(s) for acm_pca Private CA(s) |
Yes |
Launch the PostgreSQL Container
# Start Postgres Database and Mount db/init to Container
docker run --name baseca -p 5432:5432 -v /path/to/baseca/db/init:/db/init -e POSTGRES_USER=root -e POSTGRES_PASSWORD=secret -d postgres:latest
# Create Initial baseca Database and Configure Root User Account
docker exec -it baseca createdb --username=root --owner=root baseca
golang-migrate Download Instructions
brew install golang-migrate # Darwin (MacOS)
migrate -path db/migration -database "postgresql://root:secret@localhost:5432/baseca?sslmode=disable" -verbose up
# Update db/init/init-docker.sql for Admin User
VALUES (uuid_generate_v4(), 'example@example.com', crypt('ADMIN_CREDENTIALS', gen_salt('bf')), 'Example User', 'example@example.com', 'ADMIN', now());
# Run Database Init to Create Admin User
docker exec -it baseca psql -U root -d baseca -a -f db/init/init-docker.sql
This step is recommended for local testing and getting baseca
running most quickly.
Update the configuration file config.primary.local.sandbox.yml
# Update config.primary.local.sandbox.yml
database_endpoint: localhost
database_reader_endpoint: localhost
ssl_mode: disable
Start the Golang baseca
gRPC Server
database_credentials=secret go run cmd/baseca/server.go
This step is recommended for production deployments using the standard Dockerfile that is provided for baseca.
Update the configuration file config.primary.local.sandbox.yml
# IPv4_ADDRESS (Darwin)
ifconfig en0 | grep inet
# Update config.primary.local.sandbox.yml
database_endpoint: IPv4_ADDRESS
database_reader_endpoint: IPv4_ADDRESS
ssl_mode: disable
Run the baseca
Container
NOTE: You must have AWS credentials stored locally within ~/.aws
with permissions to all infrastructure components created from Terraform and access to the Private CAs.
RELEASE: Search for Latest baseca ghcr.io Published Release
and update the VERSION_SHA
container tag with the latest version.
docker run -p 9090:9090 -e database_credentials=secret -v ~/.aws/:/home/baseca/.aws/:ro \
-v /path/to/local/baseca/config:/home/baseca/config ghcr.io/coinbase/baseca:VERSION_SHA
This step is recommended for users that may want build the binary and then deploy their own custom container.
Update the configuration file config.primary.local.sandbox.yml
# Update config.primary.local.sandbox.yml
database_endpoint: localhost
database_reader_endpoint: localhost
ssl_mode: disable
Compile the Golang Binary baseca
cd /path/to/baseca
make build
# Update Path Based on AMD64 or ARM64 Architecture
database_credentials=secret ./target/bin/arm64/baseca
Start the baseca
gRPC server via the preferred method within the Local Deployment section and then run the baseca.v1.Account/LoginUser
RPC method.
Authenticate with the ADMIN
user created from the Create Initial Admin User
section.
grpcurl -vv -plaintext \
-d '{
"username": "example@example.com",
"password": "ADMIN_CREDENTIALS"
}' \
localhost:9090 baseca.v1.Account/LoginUser
# baseca.v1.Account/LoginUser Response
{
"accessToken": "[AUTH_TOKEN]",
"user": {
"username": "example@coinbase.com",
"fullName": "Example User",
"email": "example@coinbase.com",
"permissions": "ADMIN",
"credentialChangedAt": "0001-01-01T00:00:00Z",
"createdAt": "2023-05-01T12:00:00.000000Z"
}
}
export AUTH_TOKEN=[AUTH_TOKEN]
Build gRPC request to provision a service account; ensure that the environment and certificate authorities are mapped to the baseca configuration in config/config.primary.local.sandbox.yml
.
grpcurl -vv -plaintext -H "Authorization: Bearer ${AUTH_TOKEN}" \
-d '{
"service_account": "example",
"environment": "development",
"subject_alternative_names": [
"development.coinbase.com"
],
"extended_key": "EndEntityServerAuthCertificate",
"certificate_authorities": [
"development_use1"
],
"certificate_validity": 30,
"subordinate_ca": "infrastructure",
"team": "Infrastructure Security",
"email": "security@coinbase.com"
}' \
localhost:9090 baseca.v1.Service/CreateServiceAccount
# baseca.v1.Account/CreateServiceAccount Response
{
"clientId": "585c2f84-9a0e-4775-827a-a0a99c7dddcc", # Service Account UUID
"clientToken": "[CLIENT_TOKEN]", # Service Account Auth Token
"serviceAccount": "example",
"environment": "development",
"subjectAlternativeNames": ["development.coinbase.com"],
"certificateAuthorities": ["development_use1"],
"extendedKey": "EndEntityServerAuthCertificate",
"nodeAttestation": {}, # Node Attestation Not Applicable to Local Development
"certificateValidity": 30,
"subordinateCa": "infrastructure",
"team": "Infrastructure Security",
"email": "security@coinbase.com",
"createdAt": "2023-05-01T12:00:00.000000Z",
"createdBy": "830b9b81-37c0-4180-9dba-9f21b1f6ae21" # Admin User UUID
}
Mapping Between Service Account
and baseca
Configuration
After using the baseca
client to issue a certificate the private key material and signed certificate will be stored within the parameters defined within baseca.Output
. The private key material is generated locally, and because we are running the baseca
server on our local machine the Subordinate CA will also be generated and written in memory under the /tmp/baseca/ssl/[SUBORDINATE_CA]
directory.
package main
import (
"crypto/x509"
"fmt"
"log"
baseca "github.com/coinbase/baseca/pkg/client"
"github.com/coinbase/baseca/pkg/types"
)
func main() {
client, err := baseca.NewClient("localhost:9090", baseca.Attestation.Local,
baseca.WithClientId("CLIENT_ID"), baseca.WithClientToken("CLIENT_TOKEN"),
baseca.WithInsecure()) // Insecure for Local Development
if err != nil {
log.Fatal(err)
}
metadata := types.CertificateRequest{
CommonName: "development.coinbase.com",
SubjectAlternateNames: []string{"development.coinbase.com"},
SigningAlgorithm: x509.SHA512WithRSA,
PublicKeyAlgorithm: x509.RSA,
KeySize: 4096,
DistinguishedName: types.DistinguishedName{
Organization: []string{"Coinbase"},
// Additional Fields
},
Output: types.Output{
PrivateKey: "/tmp/private.key", // baseca Generate Private Key Output Location
Certificate: "/tmp/certificate.crt", // baseca Signed Leaf Certificate Output Location
IntermediateCertificateChain: "/tmp/intermediate_chain.crt", // baseca Signed Certificate Chain Up to Intermediate CA Output Location
RootCertificateChain: "/tmp/root_chain.crt", // baseca Signed Full Certificate Chain Output Location
CertificateSigningRequest: "/tmp/certificate_request.csr", // baseca CSR Output Location
},
}
response, err := client.IssueCertificate(metadata)
if err != nil {
// Handle Error
log.Fatal(err)
}
log.Printf("%+v", response)
}