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.