Skip to content
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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ toolchain go1.24.5

require (
github.com/Azure/azure-extension-foundation v0.0.0-20250620154556-caff9e3c3c5c
github.com/Azure/azure-extension-platform v0.0.0-20250107200156-aa20f765d49f
github.com/Azure/azure-extension-platform v0.0.0-20260410171604-91b4725acbb1
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ github.com/Azure/azure-extension-platform v0.0.0-20240610175536-404c704f82f8 h1:
github.com/Azure/azure-extension-platform v0.0.0-20240610175536-404c704f82f8/go.mod h1:nEQQIC3RKmMnpdc+RakYHIdu556jdcHv67ML8PdsQeQ=
github.com/Azure/azure-extension-platform v0.0.0-20250107200156-aa20f765d49f h1:ddsUz/suc9txCMz/xWOslqNMvzhbWFMTflUrbcMNoSw=
github.com/Azure/azure-extension-platform v0.0.0-20250107200156-aa20f765d49f/go.mod h1:0458BvQsi5ch6kn+KZtI5m88Z3L9UFXdoY1+6nKdivY=
github.com/Azure/azure-extension-platform v0.0.0-20260410171604-91b4725acbb1 h1:ijfz4hQtWTfTmaejzDrkNqhpCfOGFzKk/wUtPZmLrg0=
github.com/Azure/azure-extension-platform v0.0.0-20260410171604-91b4725acbb1/go.mod h1:0458BvQsi5ch6kn+KZtI5m88Z3L9UFXdoY1+6nKdivY=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M=
Expand Down
53 changes: 51 additions & 2 deletions internal/cmds/cmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ import (
"strings"
"time"

"github.com/Azure/azure-extension-platform/pkg/extensionerrors"
"github.com/Azure/azure-extension-platform/pkg/extensionevents"
"github.com/Azure/azure-extension-platform/pkg/extensionpolicysettings"
"github.com/Azure/azure-extension-platform/pkg/handlerenv"
"github.com/Azure/azure-extension-platform/pkg/hashutils"
"github.com/Azure/azure-extension-platform/pkg/logging"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming"
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
Expand All @@ -28,6 +31,7 @@ import (
"github.com/Azure/run-command-handler-linux/internal/commandProcessor"
"github.com/Azure/run-command-handler-linux/internal/constants"
"github.com/Azure/run-command-handler-linux/internal/exec"
"github.com/Azure/run-command-handler-linux/internal/extensionpolicysettingsrc"
"github.com/Azure/run-command-handler-linux/internal/files"
"github.com/Azure/run-command-handler-linux/internal/handlersettings"
"github.com/Azure/run-command-handler-linux/internal/immediatecmds"
Expand Down Expand Up @@ -210,8 +214,38 @@ func enable(ctx *log.Context, h types.HandlerEnvironment, report *types.RunComma
return "", "", err, exitCode
}

// Load extension policy settings.
// If policy file exists, load the policy. If not, then don't load.
var extensionPolicyManagerPtr *extensionpolicysettings.ExtensionPolicySettingsManager[extensionpolicysettingsrc.RCv2ExtensionPolicySettings]
policyPath := filepath.Join(h.HandlerEnvironment.ConfigFolder, constants.PolicyFileName)
var rceps *extensionpolicysettingsrc.RCv2ExtensionPolicySettings

if _, err := os.Stat(policyPath); err == nil {
extensionPolicyManagerPtr, rceps, err, exitCode = extensionpolicysettingsrc.InitializeExtensionPolicySettings(ctx, policyPath)
if err != nil {
return "", "", err, exitCode
}
Comment thread
alsanmsft marked this conversation as resolved.
ctx.Log("message", "successfully initialized extension policy settings")
} else if os.IsNotExist(err) {
ctx.Log("message", "extension policy settings file does not exist. No policy applied.", "error", err)
extensionPolicyManagerPtr = nil
} else {
return "", "", errors.Wrap(err, "failed to stat extension policy settings file"), constants.ExitCode_LoadExtensionPolicySettingsFailed
}

// Validate handler settings against policy settings.
if extensionPolicyManagerPtr != nil && rceps != nil {
if err, exitCode = extensionpolicysettingsrc.ValidateHandlerSettingsAgainstPolicy(ctx, &cfg, rceps); err != nil {
return "", "", err, exitCode
}
}

dir := filepath.Join(metadata.DownloadPath, fmt.Sprintf("%d", metadata.SeqNum))
scriptFilePath, err := downloadScript(ctx, dir, &cfg)
scriptFilePath, err := downloadScript(ctx, dir, &cfg, rceps)
if err != nil && errors.Is(err, extensionerrors.ErrItemNotInAllowlist) {
return "", "", errors.Wrap(err, "downloaded script file is not in the allowlist."), constants.ExitCode_DownloadedScriptBlockedByExtensionPolicy
}

if err != nil {
errMessage := fmt.Sprintf("Failed to download script: %v due to: %v", download.GetUriForLogging(cfg.ScriptURI()), err)
extensionEvents.LogErrorEvent("enable", errMessage)
Expand All @@ -232,6 +266,7 @@ func enable(ctx *log.Context, h types.HandlerEnvironment, report *types.RunComma

blobCreateOrReplaceError := "Error creating AppendBlob '%s' using SAS token or Managed identity. Please use a valid blob SAS URI with [read, append, create, write] permissions OR managed identity. If managed identity is used, make sure Azure blob and identity exist, and identity has been given access to storage blob's container with 'Storage Blob Data Contributor' role assignment. In case of user-assigned identity, make sure you add it under VM's identity and provide outputBlobUri / errorBlobUri and corresponding clientId in outputBlobManagedIdentity / errorBlobManagedIdentity parameter(s). In case of system-assigned identity, do not use outputBlobManagedIdentity / errorBlobManagedIdentity parameter(s). For more info, refer https://aka.ms/RunCommandManagedLinux"

// TO-DO: disable output blob if the policy settings has disableOutputBlobs set to true.
var outputBlobSASRef *storage.Blob
var outputBlobAppendClient *appendblob.Client
var outputBlobAppendCreateOrReplaceError error
Expand Down Expand Up @@ -847,7 +882,7 @@ func createDummyStatusFilesIfNeeded(ctx log.Logger, mrseqFilesNameList *list.Lis

// downloadScript downloads the script file specified in cfg into dir (creates if does
// not exist) and takes storage credentials specified in cfg into account.
func downloadScript(ctx *log.Context, dir string, cfg *handlersettings.HandlerSettings) (string, error) {
func downloadScript(ctx *log.Context, dir string, cfg *handlersettings.HandlerSettings, rceps *extensionpolicysettingsrc.RCv2ExtensionPolicySettings) (string, error) {
// - prepare the output directory for files and the command output
// - create the directory if missing
ctx.Log("event", "creating output directory", "path", dir)
Expand All @@ -872,6 +907,20 @@ func downloadScript(ctx *log.Context, dir string, cfg *handlersettings.HandlerSe
}
scriptFilePath = file
ctx.Log("event", "download complete", "output", dir)

if rceps != nil {
// Assume the downloaded script TYPE is already allowed, since this was already validated earlier in enable().
err = extensionpolicysettings.ValidateFileHashInAllowlist(scriptFilePath, rceps.DownloadedScriptsAllowlist, hashutils.HashTypeSHA256)
if err != nil {
ctx.Log("message", "downloaded script file is not in the allowlist, attempting to delete", "scriptFilePath", scriptFilePath)
if delErr := os.Remove(scriptFilePath); delErr != nil {
ctx.Log("message", "failed to delete downloaded script file", "scriptFilePath", scriptFilePath, "error", delErr)
} else {
ctx.Log("message", "successfully deleted downloaded script file", "scriptFilePath", scriptFilePath)
}
return scriptFilePath, errors.Wrapf(err, "file %s blocked by policy", scriptFilePath)
}
Comment thread
alsanmsft marked this conversation as resolved.
}
}
return scriptFilePath, nil
}
Expand Down
Loading
Loading