Service Accounts¶
Non-human identities in SystemForge are service principals: a Principal of type service with a ServicePrincipal extension. A service principal can hold credentials such as API keys and public keys, scoped to the work it does.
JWT Bearer grant (RFC 7523) is not implemented
SystemAuth does not currently accept the urn:ietf:params:oauth:grant-type:jwt-bearer grant: it is not registered with the OAuth server. Machine-to-machine clients should use the client credentials grant with an OAuth client today. The building blocks described below (service principals and keypair credentials) are what a future JWT Bearer grant will verify assertions against.
Service Principals¶
A service principal is created through the identity/principal service:
import (
"github.com/plexusone/systemforge/identity/principal"
)
svc := principal.NewService(client)
desc := "Deploys builds to staging and production"
sp, err := svc.CreateService(ctx, principal.CreateServiceInput{
Identifier: "cicd@myorg",
DisplayName: "CI/CD Pipeline",
ServiceType: "worker",
Description: &desc,
OrganizationID: &orgID,
CreatedBy: &adminPrincipalID,
AllowedScopes: []string{"deploy:staging", "deploy:production"},
AllowedIPs: []string{"10.0.0.0/8"},
})
if err != nil {
return err
}
CreateService writes the sf_principals row (type service) and its sf_service_principals extension in one transaction. Identifier is the principal's unique lookup key.
Service principals are updated with UpdateService and can be suspended and restored with Deactivate and Reactivate.
Keypair Credentials¶
Public keys are stored as credentials of type keypair in sf_credentials, owned by the principal. The private key never reaches SystemForge: generate the key pair where the service runs and register only the public key.
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"github.com/plexusone/systemforge/identity/credential"
)
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return err
}
pubBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
if err != nil {
return err
}
pubPEM := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubBytes})
creds := credential.NewService(client)
expires := time.Now().Add(365 * 24 * time.Hour)
kp, err := creds.CreateKeypair(ctx, credential.CreateKeypairInput{
PrincipalID: sp.ID,
Name: "deploy key",
KeyAlgorithm: "RS256",
PublicKeyPEM: string(pubPEM),
Scopes: []string{"deploy:staging"},
ExpiresAt: &expires,
})
if err != nil {
return err
}
// kp.KeyID is generated by SystemForge; use it as the JWT "kid" header.
Keys are looked up and managed through the same service:
| Method | Purpose |
|---|---|
CreateKeypair |
Register a public key for a principal; returns the generated key ID |
GetKeypairByKeyID |
Resolve a key by its ID (the kid a verifier receives) |
ListKeypairs |
List a principal's keys, for rotation and audit |
Revoke |
Revoke a credential by ID with a reason |
Choosing a Mechanism¶
| Need | Use |
|---|---|
| A backend calling SystemForge-protected APIs today | An OAuth client with the client credentials grant |
| A long-lived scoped secret for an integration | An API key credential on a service principal |
| Public-key authentication without shared secrets | Keypair credentials, once the JWT Bearer grant is implemented |