Skip to content

Commit

Permalink
Workload Identity: Add ability to template Subject distinguished name…
Browse files Browse the repository at this point in the history
… for X509 SVIDs (#51130)

* Add protos for DN template

* Hook up to templating engine

* Add tests and wire up cert template

* Simplify by removing optionality

* Update terraform resource
  • Loading branch information
strideynet authored Jan 21, 2025
1 parent 7721d8c commit 5edf794
Show file tree
Hide file tree
Showing 8 changed files with 589 additions and 66 deletions.
221 changes: 161 additions & 60 deletions api/gen/proto/go/teleport/workloadidentity/v1/resource.pb.go

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions api/proto/teleport/workloadidentity/v1/resource.proto
Original file line number Diff line number Diff line change
Expand Up @@ -94,13 +94,36 @@ message WorkloadIdentityRules {
repeated WorkloadIdentityRule allow = 1;
}

// Template for an X509 Distinguished Name (DN).
// Each field is optional, and, if provided, supports templating using attributes.
message X509DistinguishedNameTemplate {
// Common Name (CN) - 2.5.4.3
// If empty, the RDN will be omitted from the DN.
string common_name = 1;
// Organization (O) - 2.5.4.10
// If empty, the RDN will be omitted from the DN.
string organization = 2;
// Organizational Unit (OU) - 2.5.4.11
// If empty, the RDN will be omitted from the DN.
string organizational_unit = 3;
}

// Configuration specific to the issuance of X509-SVIDs.
message WorkloadIdentitySPIFFEX509 {
// The DNS Subject Alternative Names (SANs) that should be included in an
// X509-SVID issued using this WorkloadIdentity.
//
// Each entry in this list supports templating using attributes.
repeated string dns_sans = 1;

// Used to configure the Subject Distinguished Name (DN) of the X509-SVID.
//
// In most circumstances, it is recommended to prefer relying on the SPIFFE ID
// encoded in the URI SAN. However, the Subject DN may be needed to support
// legacy systems designed for X509 and not SPIFFE/WIMSE.
//
// If not provided, the X509-SVID will be issued with an empty Subject DN.
X509DistinguishedNameTemplate subject_template = 2;
}

// Configuration pertaining to the issuance of SPIFFE-compatible workload
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,13 @@ Optional:
Optional:

- `dns_sans` (List of String) The DNS Subject Alternative Names (SANs) that should be included in an X509-SVID issued using this WorkloadIdentity. Each entry in this list supports templating using attributes.
- `subject_template` (Attributes) Used to configure the Subject Distinguished Name (DN) of the X509-SVID. In most circumstances, it is recommended to prefer relying on the SPIFFE ID encoded in the URI SAN. However, the Subject DN may be needed to support legacy systems designed for X509 and not SPIFFE/WIMSE. If not provided, the X509-SVID will be issued with an empty Subject DN. (see [below for nested schema](#nested-schema-for-specspiffex509subject_template))

### Nested Schema for `spec.spiffe.x509.subject_template`

Optional:

- `common_name` (String) Common Name (CN) - 2.5.4.3 If empty, the RDN will be omitted from the DN.
- `organization` (String) Organization (O) - 2.5.4.10 If empty, the RDN will be omitted from the DN.
- `organizational_unit` (String) Organizational Unit (OU) - 2.5.4.11 If empty, the RDN will be omitted from the DN.

Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,13 @@ Optional:
Optional:

- `dns_sans` (List of String) The DNS Subject Alternative Names (SANs) that should be included in an X509-SVID issued using this WorkloadIdentity. Each entry in this list supports templating using attributes.
- `subject_template` (Attributes) Used to configure the Subject Distinguished Name (DN) of the X509-SVID. In most circumstances, it is recommended to prefer relying on the SPIFFE ID encoded in the URI SAN. However, the Subject DN may be needed to support legacy systems designed for X509 and not SPIFFE/WIMSE. If not provided, the X509-SVID will be issued with an empty Subject DN. (see [below for nested schema](#nested-schema-for-specspiffex509subject_template))

### Nested Schema for `spec.spiffe.x509.subject_template`

Optional:

- `common_name` (String) Common Name (CN) - 2.5.4.3 If empty, the RDN will be omitted from the DN.
- `organization` (String) Organization (O) - 2.5.4.10 If empty, the RDN will be omitted from the DN.
- `organizational_unit` (String) Organizational Unit (OU) - 2.5.4.11 If empty, the RDN will be omitted from the DN.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

39 changes: 39 additions & 0 deletions lib/auth/machineid/workloadidentityv1/decision.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,41 @@ func decide(
d.templatedWorkloadIdentity.Spec.Spiffe.X509.DnsSans[i] = templated
}

st := wi.GetSpec().GetSpiffe().GetX509().GetSubjectTemplate()
if st != nil {
dst := d.templatedWorkloadIdentity.Spec.Spiffe.X509.SubjectTemplate

templated, err = templateString(st.CommonName, attrs)
if err != nil {
d.reason = trace.Wrap(
err,
"templating spec.spiffe.x509.subject_template.common_name",
)
return d
}
dst.CommonName = templated

templated, err = templateString(st.Organization, attrs)
if err != nil {
d.reason = trace.Wrap(
err,
"templating spec.spiffe.x509.subject_template.organization",
)
return d
}
dst.Organization = templated

templated, err = templateString(st.OrganizationalUnit, attrs)
if err != nil {
d.reason = trace.Wrap(
err,
"templating spec.spiffe.x509.subject_template.organizational_unit",
)
return d
}
dst.OrganizationalUnit = templated
}

// Yay - made it to the end!
d.shouldIssue = true
return d
Expand Down Expand Up @@ -141,6 +176,10 @@ func getFieldStringValue(attrs *workloadidentityv1pb.Attrs, attr string) (string
// TODO(noah): In a coming PR, this will be replaced by evaluating the values
// within the handlebars as expressions.
func templateString(in string, attrs *workloadidentityv1pb.Attrs) (string, error) {
if len(in) == 0 {
return in, nil
}

re := regexp.MustCompile(`\{\{([^{}]+?)\}\}`)
matches := re.FindAllStringSubmatch(in, -1)

Expand Down
17 changes: 16 additions & 1 deletion lib/auth/machineid/workloadidentityv1/issuer_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,8 +349,9 @@ func x509Template(
notAfter time.Time,
spiffeID spiffeid.ID,
dnsSANs []string,
subjectTemplate *workloadidentityv1pb.X509DistinguishedNameTemplate,
) *x509.Certificate {
return &x509.Certificate{
c := &x509.Certificate{
SerialNumber: serialNumber,
NotBefore: notBefore,
NotAfter: notAfter,
Expand Down Expand Up @@ -379,6 +380,19 @@ func x509Template(
URIs: []*url.URL{spiffeID.URL()},
DNSNames: dnsSANs,
}
if subjectTemplate != nil {
c.Subject.CommonName = subjectTemplate.CommonName
if subjectTemplate.Organization != "" {
c.Subject.Organization = []string{
subjectTemplate.Organization,
}
}
if subjectTemplate.OrganizationalUnit != "" {
c.Subject.OrganizationalUnit = []string{subjectTemplate.OrganizationalUnit}
}
}

return c
}

func (s *IssuanceService) getX509CA(
Expand Down Expand Up @@ -484,6 +498,7 @@ func (s *IssuanceService) issueX509SVID(
notAfter,
spiffeID,
wid.GetSpec().GetSpiffe().GetX509().GetDnsSans(),
wid.GetSpec().GetSpiffe().GetX509().GetSubjectTemplate(),
),
ca.Cert,
pubKey,
Expand Down
Loading

0 comments on commit 5edf794

Please sign in to comment.