Skip to content
Draft
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
11 changes: 11 additions & 0 deletions apps/mql/mql.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ import (
// self-register with the vault registry. The in-memory backend is always
// available via the SDK.
_ "go.mondoo.com/mql/v13/vault/register"

// Link in the cloud token providers (AWS, GCP, Azure, GitHub) so they
// self-register for external token exchange (workload identity
// federation). They depend on cloud SDKs, so upstream does not import
// them directly; the binary opts in here.
_ "go.mondoo.com/mql/v13/providers-sdk/v1/upstream/tokenauth"

// Link in the AWS SSM Parameter Store config loader so "aws-ssm-ps://"
// config paths work. It depends on the AWS SDK, so cli/config does not
// import it directly; the binary opts in here.
_ "go.mondoo.com/mql/v13/cli/config/awsssm"
)

func main() {
Expand Down
18 changes: 14 additions & 4 deletions cli/config/aws-ssm-ps.go → cli/config/awsssm/awsssm.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
// Copyright Mondoo, Inc. 2024, 2026
// SPDX-License-Identifier: BUSL-1.1

package config
// Package awsssm loads mql/cnspec configuration from AWS SSM Parameter Store
// for config paths prefixed with "aws-ssm-ps://". It depends on the AWS SDK,
// so it is a separate package that registers itself with cli/config from
// init(); blank-import it from a binary to enable AWS SSM config loading. This
// keeps the AWS SDK out of cli/config's import graph.
package awsssm

import (
"context"
Expand All @@ -11,16 +16,21 @@ import (
"strings"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ssm"
"github.com/cockroachdb/errors"
"github.com/rs/zerolog/log"
"github.com/spf13/viper"
cliconfig "go.mondoo.com/mql/v13/cli/config"
)

const AWS_SSM_PARAMETERSTORE_PREFIX = "aws-ssm-ps://"

// loads the configuration from aws ssm parameter store
func init() {
cliconfig.RegisterRemoteConfigLoader(AWS_SSM_PARAMETERSTORE_PREFIX, loadAwsSSMParameterStore)
}

// loadAwsSSMParameterStore loads the configuration from aws ssm parameter store
func loadAwsSSMParameterStore(key string) error {
viper.RemoteConfig = &awsSSMParamConfigFactory{}
viper.SupportedRemoteProviders = []string{"aws-ssm-ps"}
Expand Down Expand Up @@ -48,7 +58,7 @@ func (a *awsSSMParamConfigFactory) Get(rp viper.RemoteProvider) (io.Reader, erro
}
ctx := context.Background()

cfg, err := config.LoadDefaultConfig(ctx)
cfg, err := awsconfig.LoadDefaultConfig(ctx)
if err != nil {
return nil, err
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright Mondoo, Inc. 2024, 2026
// SPDX-License-Identifier: BUSL-1.1

package config
package awsssm

import (
"testing"
Expand Down
5 changes: 2 additions & 3 deletions cli/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,10 @@ func InitViperConfig() {
} else {
Source = "default"
}
if strings.HasPrefix(Path, AWS_SSM_PARAMETERSTORE_PREFIX) {
err := loadAwsSSMParameterStore(Path)
if matched, err := loadRemoteConfig(Path); matched {
if err != nil {
LoadedConfig = false
log.Error().Err(err).Str("path", Path).Msg("could not load aws parameter store config")
log.Error().Err(err).Str("path", Path).Msg("could not load remote config")
} else {
LoadedConfig = true
}
Expand Down
37 changes: 37 additions & 0 deletions cli/config/remote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright Mondoo, Inc. 2024, 2026
// SPDX-License-Identifier: BUSL-1.1

package config

import "strings"

// RemoteConfigLoader loads configuration from a remote source identified by a
// URI scheme prefix (for example "aws-ssm-ps://"). It loads the resolved
// configuration into the global viper instance, mirroring the local config
// path.
type RemoteConfigLoader func(path string) error

// remoteConfigLoaders maps a scheme prefix to the loader that handles it.
var remoteConfigLoaders = map[string]RemoteConfigLoader{}

// RegisterRemoteConfigLoader associates a loader with a config path prefix.
//
// Remote backends depend on cloud SDKs (e.g. the AWS SDK for SSM Parameter
// Store), so they live in subpackages that register themselves from init().
// Binaries blank-import the backends they need; this keeps those SDKs out of
// the import graph of this package — and therefore out of every package that
// transitively imports cli/config, including the query evaluation path.
func RegisterRemoteConfigLoader(prefix string, loader RemoteConfigLoader) {
remoteConfigLoaders[prefix] = loader
}

// loadRemoteConfig dispatches to the registered loader whose prefix matches
// path. matched reports whether any loader handled it.
func loadRemoteConfig(path string) (matched bool, err error) {
for prefix, loader := range remoteConfigLoaders {
if strings.HasPrefix(path, prefix) {
return true, loader(path)
}
}
return false, nil
}
3 changes: 1 addition & 2 deletions providers-sdk/v1/upstream/sts.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
"os"
"time"

"go.mondoo.com/mql/v13/providers-sdk/v1/upstream/tokenauth"
"go.mondoo.com/mql/v13/providers/os/connection/ssh/signers"
"go.mondoo.com/ranger-rpc"
"golang.org/x/crypto/ssh"
Expand Down Expand Up @@ -66,7 +65,7 @@ func ExchangeExternalToken(apiEndpoint, audience, issuerURI, jwtToken string, to

if jwtToken == "" {
// Try to resolve a token provider from the issuer URI
provider, err := tokenauth.Resolve(issuerURI)
provider, err := resolveTokenProvider(issuerURI)
if err != nil {
return nil, err
}
Expand Down
18 changes: 14 additions & 4 deletions providers-sdk/v1/upstream/tokenauth/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@
package tokenauth

import (
"context"
"fmt"
"strings"

"go.mondoo.com/mql/v13/providers-sdk/v1/upstream"
)

// TokenProvider fetches an identity token from a cloud provider.
type TokenProvider interface {
GetToken(ctx context.Context, audience string) (string, error)
}
//
// It is an alias for upstream.TokenProvider: this package implements the
// cloud-specific providers (which pull in cloud SDKs) and registers them with
// upstream from init(), so upstream itself stays free of those dependencies.
type TokenProvider = upstream.TokenProvider

// providers maps issuer URI substrings to their TokenProvider implementation.
var providers = map[string]TokenProvider{
Expand All @@ -23,6 +26,13 @@ var providers = map[string]TokenProvider{
"sts.windows.net": &AzureTokenProvider{},
}

// init registers Resolve with upstream so external token exchange can find the
// cloud token providers without upstream importing this package (and its cloud
// SDKs). Binaries enable token exchange by blank-importing this package.
func init() {
upstream.RegisterTokenResolver(Resolve)
}

// Resolve returns the TokenProvider matching the given issuer URI.
func Resolve(issuerURI string) (TokenProvider, error) {
for key, provider := range providers {
Expand Down
52 changes: 52 additions & 0 deletions providers-sdk/v1/upstream/tokenresolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright Mondoo, Inc. 2024, 2026
// SPDX-License-Identifier: BUSL-1.1

package upstream

import (
"context"
"sync"

"github.com/cockroachdb/errors"
)

// TokenProvider fetches an identity token from a cloud provider, used by
// external token exchange (workload identity federation).
type TokenProvider interface {
GetToken(ctx context.Context, audience string) (string, error)
}

// TokenResolver returns the TokenProvider matching a given issuer URI.
type TokenResolver func(issuerURI string) (TokenProvider, error)

var (
tokenResolverMu sync.RWMutex
tokenResolver TokenResolver
)

// RegisterTokenResolver installs the resolver used by external token exchange.
//
// The cloud token providers depend on cloud SDKs (e.g. the AWS SigV4 signer),
// so they live in a separate implementation package that registers itself from
// init(). Blank-import go.mondoo.com/mql/v13/providers-sdk/v1/upstream/tokenauth
// to enable external token exchange. Keeping the registration in that package
// keeps the cloud SDKs out of the import graph of this package — and therefore
// out of inventory, which embeds upstream's protobuf types and is imported by
// any consumer of the llx.Runtime interface.
func RegisterTokenResolver(r TokenResolver) {
tokenResolverMu.Lock()
defer tokenResolverMu.Unlock()
tokenResolver = r
}

// resolveTokenProvider returns the registered TokenProvider for the issuer, or
// an error explaining how to enable external token exchange.
func resolveTokenProvider(issuerURI string) (TokenProvider, error) {
tokenResolverMu.RLock()
r := tokenResolver
tokenResolverMu.RUnlock()
if r == nil {
return nil, errors.New("external token exchange is unavailable: blank-import go.mondoo.com/mql/v13/providers-sdk/v1/upstream/tokenauth to enable it")
}
return r(issuerURI)
}
Loading