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

For miwi use user delegated sas to allow bootstrap node to pull the ignition #186

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
46 changes: 46 additions & 0 deletions pkg/cluster/graph/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@ package graph
import (
"context"
"encoding/json"
"fmt"
"io"
"time"

"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service"
"github.com/openshift/installer/pkg/asset/ignition/bootstrap"
"github.com/sirupsen/logrus"

Expand All @@ -27,6 +32,7 @@ type Manager interface {
Exists(ctx context.Context, resourceGroup, account string) (bool, error)
Save(ctx context.Context, resourceGroup, account string, g Graph) error
LoadPersisted(ctx context.Context, resourceGroup, account string) (PersistedGraph, error)
GetUserDelegatedSASIgnitionBlobURL(ctx context.Context, resourceGroup, account, blobURL string, usesWorkloadIdentity bool) (string, error)
}

type manager struct {
Expand Down Expand Up @@ -121,3 +127,43 @@ func (m *manager) LoadPersisted(ctx context.Context, resourceGroup, account stri

// SavePersistedGraph could be implemented and used with care if needed, but
// currently we don't need it (and it's better that way)

// GetUserDelegatedSASIgnitionBlobURL is used for MIWI clusters so that Ignition blob can be accessed by bootstrap VM without Storage Account Shared Access Keys
func (m *manager) GetUserDelegatedSASIgnitionBlobURL(ctx context.Context, resourceGroup, account, blobURL string, usesWorkloadIdentity bool) (string, error) {
if !usesWorkloadIdentity {
return "", fmt.Errorf("getUserDelegatedSASIgnitionBlobURL called for a Cluster Service Principal cluster")
}
urlParts, err := sas.ParseURL(blobURL)
if err != nil {
return "", err
}
currentTime := time.Now().UTC().Add(-10 * time.Second)
expiryTime := time.Now().UTC().Add(time.Hour)
perms := sas.BlobPermissions{Read: true}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed that for CSP clusters we give "read" and "list", so why does only "read" work here?

signatureValues := sas.BlobSignatureValues{
Protocol: sas.ProtocolHTTPS,
StartTime: currentTime,
ExpiryTime: expiryTime,
Permissions: perms.String(),
ContainerName: ignitionContainer,
BlobName: ignitionBlob,
}

info := service.KeyInfo{
Start: to.Ptr(currentTime.UTC().Format(sas.TimeFormat)),
Expiry: to.Ptr(expiryTime.UTC().Format(sas.TimeFormat)),
}
client, err := m.storage.BlobService(ctx, resourceGroup, account, armstorage.Permissions(""), armstorage.SignedResourceTypes(""))
if err != nil {
return "", err
}
udc, err := client.ServiceClient().GetUserDelegationCredential(ctx, info, nil)
if err != nil {
return "", err
}
urlParts.SAS, err = signatureValues.SignWithUserDelegation(udc)
if err != nil {
return "", err
}
return urlParts.String(), nil
}
42 changes: 29 additions & 13 deletions pkg/installer/deployresources.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,38 @@ func (m *manager) deployResourceTemplate(ctx context.Context) error {
return err
}

params := map[string]interface{}{}
var paramType string

if m.oc.UsesWorkloadIdentity() {
paramType = "secureString"
sasURL, err := m.graph.GetUserDelegatedSASIgnitionBlobURL(ctx, resourceGroup, account, `https://cluster`+m.oc.Properties.StorageSuffix+`.blob.`+m.env.Environment().StorageEndpointSuffix+`/ignition/bootstrap.ign`, m.oc.UsesWorkloadIdentity())
if err != nil {
return err
}
params["sas"] = map[string]string{
"value": sasURL,
}
} else {
paramType = "object"
params["sas"] = map[string]interface{}{
"value": map[string]interface{}{
"signedStart": m.oc.Properties.Install.Now.Format(time.RFC3339),
"signedExpiry": m.oc.Properties.Install.Now.Add(24 * time.Hour).Format(time.RFC3339),
"signedPermission": "rl",
"signedResourceTypes": "o",
"signedServices": "b",
"signedProtocol": "https",
},
}
}

t := &arm.Template{
Schema: "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
ContentVersion: "1.0.0.0",
Parameters: map[string]*arm.TemplateParameter{
"sas": {
Type: "object",
Type: paramType,
},
},
Resources: []*arm.Resource{
Expand All @@ -52,18 +78,8 @@ func (m *manager) deployResourceTemplate(ctx context.Context) error {
m.computeMasterVMs(installConfig, zones, machineMaster),
},
}
return arm.DeployTemplate(ctx, m.log, m.deployments, resourceGroup, "resources", t, map[string]interface{}{
"sas": map[string]interface{}{
"value": map[string]interface{}{
"signedStart": m.oc.Properties.Install.Now.Format(time.RFC3339),
"signedExpiry": m.oc.Properties.Install.Now.Add(24 * time.Hour).Format(time.RFC3339),
"signedPermission": "rl",
"signedResourceTypes": "o",
"signedServices": "b",
"signedProtocol": "https",
},
},
})

return arm.DeployTemplate(ctx, m.log, m.deployments, resourceGroup, "resources", t, params)
}

// zones configures how master nodes are distributed across availability zones. In regions where the number of zones matches
Expand Down
11 changes: 8 additions & 3 deletions pkg/installer/deployresources_resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,16 @@ func (m *manager) networkMasterNICs(installConfig *installconfig.InstallConfig)
}

func (m *manager) computeBootstrapVM(installConfig *installconfig.InstallConfig) *arm.Resource {
var customData string
var customData, sasURL string
if m.oc.UsesWorkloadIdentity() {
sasURL = `',parameters('sas'),'`
} else {
sasURL = `https://cluster` + m.oc.Properties.StorageSuffix + `.blob.` + m.env.Environment().StorageEndpointSuffix + `/ignition/bootstrap.ign?', listAccountSas(resourceId('Microsoft.Storage/storageAccounts', 'cluster` + m.oc.Properties.StorageSuffix + `'), '2019-04-01', parameters('sas')).accountSasToken, '`
}
if m.oc.Properties.NetworkProfile.GatewayPrivateEndpointIP != "" {
customData = `[base64(concat('{"ignition":{"version":"3.2.0","proxy":{"httpsProxy":"http://` + m.oc.Properties.NetworkProfile.GatewayPrivateEndpointIP + `"},"config":{"replace":{"source":"https://cluster` + m.oc.Properties.StorageSuffix + `.blob.` + m.env.Environment().StorageEndpointSuffix + `/ignition/bootstrap.ign?', listAccountSas(resourceId('Microsoft.Storage/storageAccounts', 'cluster` + m.oc.Properties.StorageSuffix + `'), '2019-04-01', parameters('sas')).accountSasToken, '"}}}}'))]`
customData = `[base64(concat('{"ignition":{"version":"3.2.0","proxy":{"httpsProxy":"http://` + m.oc.Properties.NetworkProfile.GatewayPrivateEndpointIP + `"},"config":{"replace":{"source":"` + sasURL + `"}}}}'))]`
} else {
customData = `[base64(concat('{"ignition":{"version":"3.2.0","config":{"replace":{"source":"https://cluster` + m.oc.Properties.StorageSuffix + `.blob.` + m.env.Environment().StorageEndpointSuffix + `/ignition/bootstrap.ign?', listAccountSas(resourceId('Microsoft.Storage/storageAccounts', 'cluster` + m.oc.Properties.StorageSuffix + `'), '2019-04-01', parameters('sas')).accountSasToken, '"}}}}'))]`
customData = `[base64(concat('{"ignition":{"version":"3.2.0","config":{"replace":{"source":"` + sasURL + `"}}}}'))]`
}

vm := &mgmtcompute.VirtualMachine{
Expand Down
2 changes: 2 additions & 0 deletions pkg/util/azureclient/azuresdk/azblob/blobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service"
)

// BlobsClient is a minimal interface for Azure BlobsClient
type BlobsClient interface {
DownloadStream(ctx context.Context, containerName string, blobName string, o *azblob.DownloadStreamOptions) (azblob.DownloadStreamResponse, error)
UploadBuffer(ctx context.Context, containerName string, blobName string, buffer []byte, o *azblob.UploadBufferOptions) (azblob.UploadBufferResponse, error)
DeleteBlob(ctx context.Context, containerName string, blobName string, o *azblob.DeleteBlobOptions) (azblob.DeleteBlobResponse, error)
ServiceClient() *service.Client
BlobsClientAddons
}

Expand Down
15 changes: 15 additions & 0 deletions pkg/util/mocks/graph/graph.go

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