server.garden privileged automation agent (mirror of https://git.sequentialread.com/forest/rootsystem)
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
303 lines
9.8 KiB
303 lines
9.8 KiB
package pki |
|
|
|
import ( |
|
"crypto/x509" |
|
"crypto/x509/pkix" |
|
"encoding/pem" |
|
"fmt" |
|
"io/ioutil" |
|
"os" |
|
"path/filepath" |
|
"time" |
|
|
|
"git.sequentialread.com/forest/easypki.git/pkg/certificate" |
|
"git.sequentialread.com/forest/easypki.git/pkg/easypki" |
|
"git.sequentialread.com/forest/easypki.git/pkg/store" |
|
"git.sequentialread.com/forest/rootsystem/configuration" |
|
"git.sequentialread.com/forest/rootsystem/objectStorage" |
|
"github.com/pkg/errors" |
|
) |
|
|
|
func BuildTLSCertsForThreshold( |
|
workingDirectory string, |
|
domain string, |
|
clientId string, |
|
storage objectStorage.ObjectStorager, |
|
) error { |
|
|
|
inMemoryStore := &store.InMemory{} |
|
pki := &easypki.EasyPKI{Store: inMemoryStore} |
|
|
|
//pkiBytes, err := json.MarshalIndent(inMemoryStore.CAs, "", " ") |
|
|
|
clientCA := fmt.Sprintf("%s_CA", clientId) |
|
domainCA := fmt.Sprintf("%s_CA", domain) |
|
thresholdServerConfigRole := filepath.Join( |
|
workingDirectory, |
|
configuration.ANSIBLE_ROLES, |
|
"threshold-server-config/files", |
|
) |
|
thresholdClientConfigRole := filepath.Join( |
|
workingDirectory, |
|
configuration.ANSIBLE_ROLES, |
|
"threshold-client-config/files", |
|
) |
|
thresholdRegisterClientWithServerConfigRole := filepath.Join( |
|
workingDirectory, |
|
configuration.ANSIBLE_ROLES, |
|
"threshold-register-client-with-server/files", |
|
) |
|
|
|
for _, path := range []string{ |
|
thresholdServerConfigRole, |
|
thresholdClientConfigRole, |
|
thresholdRegisterClientWithServerConfigRole, |
|
} { |
|
if _, err := os.Stat(path); os.IsNotExist(err) { |
|
err = os.Mkdir(path, 0600) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): failed trying to ensure files folder exists in ansible role") |
|
} |
|
} |
|
} |
|
|
|
domainCACertFile, notFound, err := storage.Get(fmt.Sprintf("threshold/%s.crt", domainCA)) |
|
if err != nil && !notFound { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): failed trying to get domain CA from object storage:") |
|
} |
|
domainCACertLocalPath := filepath.Join(thresholdClientConfigRole, fmt.Sprintf("%s.crt", domainCA)) |
|
domainKeyLocalPath := filepath.Join(thresholdServerConfigRole, fmt.Sprintf("%s.key", domain)) |
|
domainCertLocalPath := filepath.Join(thresholdServerConfigRole, fmt.Sprintf("%s.crt", domain)) |
|
|
|
// if the file was not already uploaded to object storage, that must mean |
|
// we are the first node to run -- therefore we must create and upload it |
|
if notFound { |
|
// Create a CA for the server's key/cert |
|
err = pki.Sign( |
|
nil, |
|
&easypki.Request{ |
|
Name: domainCA, |
|
Template: &x509.Certificate{ |
|
NotAfter: time.Now().Add(time.Hour * 24 * 720), |
|
IsCA: true, |
|
MaxPathLen: -1, |
|
Subject: getSubject(domainCA), |
|
}, |
|
}, |
|
) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): failed creating CA") |
|
} |
|
|
|
err = saveBundle(pki, domainCA, domainCA, false, thresholdClientConfigRole) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): saveBundle():") |
|
} |
|
|
|
// Upload the server CA certificate to the object storage so the other client(s) can use it |
|
bytes, err := ioutil.ReadFile(domainCACertLocalPath) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): cant read domainCA file from thresholdClientConfigRole:") |
|
} |
|
err = storage.Put(fmt.Sprintf("threshold/%s.crt", domainCA), bytes) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): uploading domainCA file to object storage failed:") |
|
} |
|
|
|
caSigner, err := pki.GetCA(domainCA) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): signer named \"CA\" was not found") |
|
} |
|
|
|
// Create Threshold server certificate |
|
err = pki.Sign( |
|
caSigner, |
|
&easypki.Request{ |
|
Name: domain, |
|
Template: &x509.Certificate{ |
|
NotAfter: time.Now().Add(time.Hour * 24 * 720), |
|
IsCA: false, |
|
Subject: getSubject(domain), |
|
DNSNames: []string{domain}, |
|
}, |
|
}, |
|
) |
|
|
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): failed creating Threshold server certificate") |
|
} |
|
|
|
err = saveBundle(pki, domainCA, domain, true, thresholdServerConfigRole) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): saveBundle():") |
|
} |
|
|
|
// Upload the server key/cert to the object storage so the other client(s) can use it |
|
bytes, err = ioutil.ReadFile(domainKeyLocalPath) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): cant read domainCA file from thresholdClientConfigRole:") |
|
} |
|
err = storage.Put(fmt.Sprintf("threshold/%s.key", domain), bytes) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): uploading domainCA file to object storage failed:") |
|
} |
|
|
|
bytes, err = ioutil.ReadFile(domainCertLocalPath) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): cant read domainCA file from thresholdClientConfigRole:") |
|
} |
|
err = storage.Put(fmt.Sprintf("threshold/%s.crt", domain), bytes) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): uploading domainCA file to object storage failed:") |
|
} |
|
|
|
// else, if the domainCACertFile does exist in object storage, that means another node already created and uploaded it. |
|
// just in case this node wants to run the global terraform project, we should download the cert/key too. |
|
// storing them in the object storage should be fine security wise because its not a very high security cert |
|
// for example, we are uploading it to a cloud provider anyway, and we don't fully trust the threshold server anyways |
|
// since it runs on cloud, on someone elses computer |
|
|
|
} else { |
|
err = ioutil.WriteFile(domainCACertLocalPath, domainCACertFile.Content, 0600) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): writing domainCA file to ansible role failed") |
|
} |
|
|
|
domainCertFile, notFound, err := storage.Get(fmt.Sprintf("threshold/%s.crt", domain)) |
|
if err != nil || notFound { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): failed trying to get domainCertFile from object storage:") |
|
} |
|
domainKeyFile, notFound, err := storage.Get(fmt.Sprintf("threshold/%s.key", domain)) |
|
if err != nil || notFound { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): failed trying to get domainCertFile from object storage:") |
|
} |
|
|
|
err = ioutil.WriteFile(domainCertLocalPath, domainCertFile.Content, 0600) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): writing domainCertFile to ansible role failed") |
|
} |
|
err = ioutil.WriteFile(domainKeyLocalPath, domainKeyFile.Content, 0600) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): writing domainCertFile to ansible role failed") |
|
} |
|
} |
|
|
|
// Create A CA for this client |
|
err = pki.Sign( |
|
nil, |
|
&easypki.Request{ |
|
Name: clientCA, |
|
Template: &x509.Certificate{ |
|
NotAfter: time.Now().Add(time.Hour * 24 * 720), |
|
IsCA: true, |
|
MaxPathLen: -1, |
|
Subject: getSubject(clientCA), |
|
}, |
|
}, |
|
) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): failed creating CA") |
|
} |
|
|
|
err = saveBundle(pki, clientCA, clientCA, false, thresholdRegisterClientWithServerConfigRole) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): saveBundle():") |
|
} |
|
|
|
clientCASigner, err := pki.GetCA(clientCA) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): signer named \"CA\" was not found") |
|
} |
|
|
|
// Create Threshold client certificates |
|
clientEmail := fmt.Sprintf("%s@%s", clientId, domain) |
|
err = pki.Sign( |
|
clientCASigner, |
|
&easypki.Request{ |
|
Name: clientEmail, |
|
Template: &x509.Certificate{ |
|
NotAfter: time.Now().Add(time.Hour * 24 * 720), |
|
IsCA: false, |
|
Subject: getSubject(clientEmail), |
|
EmailAddresses: []string{clientEmail}, |
|
}, |
|
}, |
|
) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): failed creating Threshold client certificate") |
|
} |
|
|
|
err = saveBundle(pki, clientCA, clientEmail, true, thresholdClientConfigRole) |
|
if err != nil { |
|
return errors.Wrap(err, "BuildTLSCertsForThreshold(): saveBundle():") |
|
} |
|
|
|
return nil |
|
} |
|
|
|
func saveBundle(pki *easypki.EasyPKI, caName, bundleName string, savePrivateKey bool, saveInDirectory string) error { |
|
var bundle *certificate.Bundle |
|
if caName == "" { |
|
caName = bundleName |
|
} |
|
|
|
bundle, err := pki.GetBundle(caName, bundleName) |
|
if err != nil { |
|
return errors.Wrapf(err, "Failed getting bundle %v within CA %v: %v", bundleName, caName) |
|
} |
|
leaf := bundle |
|
chain := []*certificate.Bundle{bundle} |
|
//if fullChain { |
|
for { |
|
if leaf.Cert.Issuer.CommonName == leaf.Cert.Subject.CommonName { |
|
break |
|
} |
|
ca, err := pki.GetCA(leaf.Cert.Issuer.CommonName) |
|
if err != nil { |
|
return errors.Wrapf(err, "Failed getting signing CA %v: %v", leaf.Cert.Issuer.CommonName) |
|
} |
|
chain = append(chain, ca) |
|
leaf = ca |
|
} |
|
//} |
|
|
|
if savePrivateKey { |
|
key, err := os.Create(filepath.Join(saveInDirectory, bundleName+".key")) |
|
if err != nil { |
|
return errors.Wrap(err, "Failed creating key output file") |
|
} |
|
if err := pem.Encode(key, &pem.Block{ |
|
Bytes: x509.MarshalPKCS1PrivateKey(bundle.Key), |
|
Type: "RSA PRIVATE KEY", |
|
}); err != nil { |
|
return errors.Wrap(err, "Failed ecoding private key") |
|
} |
|
} |
|
|
|
crtName := bundleName + ".crt" |
|
cert, err := os.Create(filepath.Join(saveInDirectory, crtName)) |
|
if err != nil { |
|
return errors.Wrap(err, "Failed creating chain output file") |
|
} |
|
for _, c := range chain { |
|
if err := pem.Encode(cert, &pem.Block{ |
|
Bytes: c.Cert.Raw, |
|
Type: "CERTIFICATE", |
|
}); err != nil { |
|
return errors.Wrapf(err, "Failed ecoding %v certificate: %v", c.Name) |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
// TODO add certificate metadata option to seedpacket? |
|
func getSubject(commonName string) pkix.Name { |
|
return pkix.Name{ |
|
Organization: []string{"user left blank"}, |
|
OrganizationalUnit: []string{"user left blank"}, |
|
Locality: []string{"user left blank"}, |
|
Country: []string{"user left blank"}, |
|
Province: []string{"user left blank"}, |
|
CommonName: commonName, |
|
} |
|
}
|
|
|