Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Populate ClientID and ObjectID of cluster and platform workload identities #3860

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/cluster/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ type manager struct {
fpPrivateEndpoints network.PrivateEndpointsClient // TODO: use armFPPrivateEndpoints instead.
armFPPrivateEndpoints armnetwork.PrivateEndpointsClient
armRPPrivateLinkServices armnetwork.PrivateLinkServicesClient
userAssignedIdentities armmsi.UserAssignedIdentitiesClient

dns dns.Manager
storage storage.Manager
Expand Down
83 changes: 81 additions & 2 deletions pkg/cluster/clustermsi.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@ import (
"errors"
"fmt"
"net/http"
"strings"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/msi-dataplane/pkg/dataplane"
"github.com/Azure/msi-dataplane/pkg/dataplane/swagger"
"github.com/Azure/msi-dataplane/pkg/store"

"github.com/Azure/ARO-RP/pkg/api"
"github.com/Azure/ARO-RP/pkg/env"
"github.com/Azure/ARO-RP/pkg/util/azureclient/azuresdk/armmsi"
)
Expand All @@ -22,6 +25,10 @@ const (
mockMsiCertValidityDays = 90
)

var (
errClusterMsiNotPresentInResponse = errors.New("cluster msi not present in msi credentials response")
)

// ensureClusterMsiCertificate leverages the MSI dataplane module to fetch the MSI's
// backing certificate (if needed) and store the certificate in the cluster MSI key
// vault. It does not concern itself with whether an existing certificate is valid
Expand Down Expand Up @@ -61,12 +68,16 @@ func (m *manager) ensureClusterMsiCertificate(ctx context.Context) error {
if m.env.FeatureIsSet(env.FeatureUseMockMsiRp) {
expirationDate = now.AddDate(0, 0, mockMsiCertValidityDays)
} else {
if msiCredObj.CredentialsObject.ExplicitIdentities == nil || len(msiCredObj.CredentialsObject.ExplicitIdentities) == 0 || msiCredObj.CredentialsObject.ExplicitIdentities[0] == nil || msiCredObj.CredentialsObject.ExplicitIdentities[0].NotAfter == nil {
identity, err := getSingleExplicitIdentity(msiCredObj)
if err != nil {
return err
}
if identity.NotAfter == nil {
return errors.New("unable to pull NotAfter from the MSI CredentialsObject")
}

// The swagger API spec for the MI RP specifies that NotAfter will be "in the format 2017-03-01T14:11:00Z".
expirationDate, err = time.Parse(time.RFC3339, *msiCredObj.CredentialsObject.ExplicitIdentities[0].NotAfter)
expirationDate, err = time.Parse(time.RFC3339, *identity.NotAfter)
if err != nil {
return err
}
Expand Down Expand Up @@ -123,7 +134,13 @@ func (m *manager) initializeClusterMsiClients(ctx context.Context) error {
return err
}

userAssignedIdentities, err := armmsi.NewUserAssignedIdentitiesClient(subId, azureCred, clientOptions)
if err != nil {
return err
}

m.clusterMsiFederatedIdentityCredentials = clusterMsiFederatedIdentityCredentials
m.userAssignedIdentities = userAssignedIdentities
return nil
}

Expand All @@ -137,3 +154,65 @@ func (m *manager) clusterMsiSecretName() (string, error) {

return fmt.Sprintf("%s-%s", m.doc.ID, clusterMsi.Name), nil
}

func (m *manager) clusterIdentityIDs(ctx context.Context) error {
if !m.doc.OpenShiftCluster.UsesWorkloadIdentity() {
return fmt.Errorf("clusterIdentityIDs called for CSP cluster")
}

clusterMsiResourceId, err := m.doc.OpenShiftCluster.ClusterMsiResourceId()
if err != nil {
return err
}

uaMsiRequest := dataplane.UserAssignedMSIRequest{
IdentityURL: m.doc.OpenShiftCluster.Identity.IdentityURL,
ResourceIDs: []string{clusterMsiResourceId.String()},
TenantID: m.doc.OpenShiftCluster.Identity.TenantID,
}

msiCredObj, err := m.msiDataplane.GetUserAssignedIdentities(ctx, uaMsiRequest)
if err != nil {
return err
}

identity, err := getSingleExplicitIdentity(msiCredObj)
if err != nil {
return err
}
if identity.ClientID == nil || identity.ObjectID == nil {
return fmt.Errorf("unable to pull clientID and objectID from the MSI CredentialsObject")
}

m.doc, err = m.db.PatchWithLease(ctx, m.doc.Key, func(doc *api.OpenShiftClusterDocument) error {
// we iterate through the existing identities to find the identity matching
// the expected resourceID with casefolding, to ensure we preserve the
// passed-in casing on IDs even if it may be incorrect
for k, v := range doc.OpenShiftCluster.Identity.UserAssignedIdentities {
if strings.EqualFold(k, clusterMsiResourceId.String()) {
v.ClientID = *identity.ClientID
v.PrincipalID = *identity.ObjectID

doc.OpenShiftCluster.Identity.UserAssignedIdentities[k] = v
return nil
}
}

return fmt.Errorf("no entries found matching clusterMsiResourceId")
})

return err
}

// We expect the GetUserAssignedIdentities request to only ever be made for one identity
// at a time (the cluster MSI) and thus we expect the response to only contain a single
// identity's details.
func getSingleExplicitIdentity(msiCredObj *dataplane.UserAssignedIdentities) (*swagger.NestedCredentialsObject, error) {
tsatam marked this conversation as resolved.
Show resolved Hide resolved
if msiCredObj.ExplicitIdentities == nil ||
len(msiCredObj.ExplicitIdentities) == 0 ||
msiCredObj.ExplicitIdentities[0] == nil {
return nil, errClusterMsiNotPresentInResponse
}

return msiCredObj.ExplicitIdentities[0], nil
}
Loading
Loading