Enroll() from the Go SDK will be the best reference. Let's translate this to Python or Ruby or both.
func Enroll(enFlags EnrollmentFlags) (*ziti.Config, error) {
var key crypto.PrivateKey
var err error
cfg := &ziti.Config{
ZtAPI: edge_apis.ClientUrl(enFlags.Token.Issuer),
}
if strings.TrimSpace(enFlags.KeyFile) != "" {
stat, err := os.Stat(enFlags.KeyFile)
if stat != nil && !os.IsNotExist(err) {
if stat.IsDir() {
return nil, errors.Errorf("specified key is a directory (%s)", enFlags.KeyFile)
}
if absPath, fileErr := filepath.Abs(enFlags.KeyFile); fileErr != nil {
return nil, fileErr
} else {
cfg.ID.Key = "file://" + absPath
}
} else {
cfg.ID.Key = enFlags.KeyFile
pfxlog.Logger().Infof("using engine : %s\n", strings.Split(enFlags.KeyFile, ":")[0])
}
} else {
var asnBytes []byte
var keyPem []byte
if enFlags.KeyAlg.EC() {
key, err = generateECKey()
asnBytes, _ := x509.MarshalECPrivateKey(key.(*ecdsa.PrivateKey))
keyPem = pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: asnBytes})
} else if enFlags.KeyAlg.RSA() {
key, err = generateRSAKey()
asnBytes = x509.MarshalPKCS1PrivateKey(key.(*rsa.PrivateKey))
keyPem = pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: asnBytes})
} else {
panic(fmt.Sprintf("invalid KeyAlg specified: %s", enFlags.KeyAlg.Get()))
}
cfg.ID.Key = "pem:" + string(keyPem)
if err != nil {
return nil, err
}
}
if enFlags.CertFile != "" {
enFlags.CertFile, _ = filepath.Abs(enFlags.CertFile)
cfg.ID.Cert = "file://" + enFlags.CertFile
}
caPool, allowedCerts := enFlags.GetCertPool()
//fetch so CA bundles
pfxlog.Logger().Debug("fetching certificates from server")
serverOnlyCaPool := x509.NewCertPool()
serverOnlyCaPool.AddCert(enFlags.Token.SignatureCert)
controllerCas := FetchCertificates(cfg.ZtAPI, serverOnlyCaPool)
if len(controllerCas) == 0 {
return nil, errors.New("expected 1 or more CAs from controller, got 0")
}
for _, cert := range controllerCas {
allowedCerts = append(allowedCerts, cert)
caPool.AddCert(cert)
}
var enrollErr error
switch enFlags.Token.EnrollmentMethod {
case "ott":
enrollErr = enrollOTT(enFlags.Token, cfg, caPool)
case "ottca":
enrollErr = enrollCA(enFlags.Token, cfg, caPool)
case "ca":
enrollErr = enrollCAAuto(enFlags, cfg, caPool)
default:
enrollErr = errors.Errorf("enrollment method '%s' is not supported", enFlags.Token.EnrollmentMethod)
}
if enrollErr != nil {
return nil, enrollErr
}
if len(allowedCerts) > 0 {
var buf bytes.Buffer
err := nfx509.MarshalToPem(allowedCerts, &buf)
if err != nil {
return nil, err
}
cfg.ID.CA = "pem:" + buf.String()
}
cfg.Credentials = edge_apis.NewIdentityCredentialsFromConfig(cfg.ID)
return cfg, nil
}
This code snippet defines a function called Enroll in Go. The function takes an EnrollmentFlags parameter and returns a Ziti context, which is a data structure containing an identity typically stored as JSON.
The function starts by checking whether a private key is provided and generates one if not. The same function can be used to enroll an identity with a certificate from a trusted authority or to request a certificate from the edge enrollment signer CA managed by the Ziti controller, so there's an affordance here for an cert to be provided instead of issued.
The function then fetches the well-known (trusted) certificates from the client API provided by the controller.
Based on the enFlags.Token.EnrollmentMethod value, the function calls different enrollment methods (enrollOTT, enrollCA, enrollCAAuto) and assigns the result to the enrollErr variable.
We'll follow the enrollOTT possibility here because it's the most relevant. This ott method is called when enrolling with a one time token (JWT) and requesting a certificate to be issued.
Here's a summary of what enrollOTT does:
- It loads the private key.
- It creates a certificate request as PEM.
- It sends a POST request to the enrollment URL specified in
token.EnrolmentUrl() with the certificate request in PEM format.
- If the status code is
http.StatusOK, it checks the content-type header. If it's application/json, it parses the response body as JSON and extracts the data.cert field, which is assumed to contain the certificate in PEM format. It then sets cfg.ID.Cert to this value.
- If the
content-type header is not application/json, it treats the response body as PEM and sets cfg.ID.Cert to this value.
Finally, the function returns a Ziti context object from the key, cert, and well-known pool.