Forward Scriptorium credentials through the shared adapter path
This commit is contained in:
@@ -220,6 +220,7 @@ type RenderConfig struct {
|
||||
|
||||
// ScriptoriumConfig configures Scriptorium-backed artifact generation.
|
||||
type ScriptoriumConfig struct {
|
||||
Credentials []configkit.Secret `yaml:"credentials,omitempty" json:"credentials,omitempty"`
|
||||
Binary string `yaml:"binary" json:"binary"`
|
||||
ConfigPath string `yaml:"config_path" json:"config_path"`
|
||||
Timeout string `yaml:"timeout" json:"timeout"`
|
||||
|
||||
@@ -73,6 +73,24 @@ func (cfg NotariusConfig) MarshalYAML() (any, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MarshalYAML exposes only public Scriptorium credential reference names.
|
||||
func (cfg ScriptoriumConfig) MarshalYAML() (any, error) {
|
||||
type safeScriptorium struct {
|
||||
Binary string `yaml:"binary"`
|
||||
ConfigPath string `yaml:"config_path"`
|
||||
Timeout string `yaml:"timeout"`
|
||||
RenderDebug bool `yaml:"render_debug"`
|
||||
Credentials []string `yaml:"credentials,omitempty"`
|
||||
Artifacts map[string]ScriptoriumArtifactConfig `yaml:"artifacts"`
|
||||
ArtifactFamilies map[string]ScriptoriumArtifactFamilyConfig `yaml:"artifact_families"`
|
||||
}
|
||||
return safeScriptorium{
|
||||
Binary: cfg.Binary, ConfigPath: cfg.ConfigPath, Timeout: cfg.Timeout,
|
||||
RenderDebug: cfg.RenderDebug, Credentials: secretReferenceNames(cfg.Credentials),
|
||||
Artifacts: cfg.Artifacts, ArtifactFamilies: cfg.ArtifactFamilies,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func secretReferenceNames(secrets []configkit.Secret) []string {
|
||||
if secrets == nil {
|
||||
return nil
|
||||
|
||||
@@ -30,6 +30,7 @@ type effectivePipelineProjection struct {
|
||||
}
|
||||
|
||||
type effectiveScriptoriumConfig struct {
|
||||
Credentials []string `yaml:"credentials,omitempty" json:"credentials,omitempty"`
|
||||
Binary string `yaml:"binary" json:"binary"`
|
||||
ConfigPath string `yaml:"config_path" json:"config_path"`
|
||||
Timeout string `yaml:"timeout" json:"timeout"`
|
||||
@@ -52,7 +53,8 @@ func projectEffectivePipeline(cfg *PipelineConfig, logicalNotarius bool) effecti
|
||||
}
|
||||
if cfg.Scriptorium != nil {
|
||||
projection.Scriptorium = &effectiveScriptoriumConfig{
|
||||
Binary: cfg.Scriptorium.Binary, ConfigPath: cfg.Scriptorium.ConfigPath,
|
||||
Credentials: secretReferenceNames(cfg.Scriptorium.Credentials),
|
||||
Binary: cfg.Scriptorium.Binary, ConfigPath: cfg.Scriptorium.ConfigPath,
|
||||
Timeout: cfg.Scriptorium.Timeout, RenderDebug: cfg.Scriptorium.RenderDebug,
|
||||
Artifacts: cfg.Scriptorium.Artifacts,
|
||||
}
|
||||
|
||||
65
internal/config/scriptorium_credentials_test.go
Normal file
65
internal/config/scriptorium_credentials_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestScriptoriumCredentialsComposeAndSerializeSafely(t *testing.T) {
|
||||
t.Setenv("OPENROUTER_API_KEY", "synthetic-first-secret")
|
||||
t.Setenv("CUSTOM_PROVIDER_KEY", "synthetic-second-secret")
|
||||
dir := t.TempDir()
|
||||
root := writePipelineFixture(t, dir, "pipeline.yml", "whisperx:\n transcribe_url: https://example.test/transcribe\nscriptorium:\n credentials: [OPENROUTER_API_KEY]\n")
|
||||
base, err := LoadPipeline(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, marshal := range []func() ([]byte, error){
|
||||
func() ([]byte, error) { return yaml.Marshal(base) },
|
||||
func() ([]byte, error) { return MarshalEffectivePipeline(base) },
|
||||
} {
|
||||
data, err := marshal()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(string(data), "OPENROUTER_API_KEY") || strings.Contains(string(data), "synthetic-first-secret") {
|
||||
t.Fatal("unsafe or missing credential projection")
|
||||
}
|
||||
}
|
||||
t.Setenv("OPENROUTER_API_KEY", "rotated-secret")
|
||||
rotated, err := LoadPipeline(root)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if EffectivePipelineDigest(base) != EffectivePipelineDigest(rotated) {
|
||||
t.Fatal("rotation changed effective digest")
|
||||
}
|
||||
for _, tc := range []struct{ name, value, want string }{
|
||||
{"replace", "[CUSTOM_PROVIDER_KEY]", "CUSTOM_PROVIDER_KEY"},
|
||||
{"clear", "[]", ""}, {"null", "null", ""},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
writePipelineFixture(t, dir, "overlays/"+tc.name+".yml", "scriptorium:\n credentials: "+tc.value+"\n")
|
||||
loaded, err := LoadPipelineWithOptions(root, PipelineLoadOptions{Overlay: &tc.name})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
creds := loaded.Scriptorium.Credentials
|
||||
if tc.want == "" {
|
||||
if len(creds) != 0 {
|
||||
t.Fatal("credentials not cleared")
|
||||
}
|
||||
return
|
||||
}
|
||||
if len(creds) != 1 || creds[0].EnvName() != tc.want || creds[0].Value() != "synthetic-second-secret" {
|
||||
t.Fatal("overlay did not replace resolved credentials")
|
||||
}
|
||||
})
|
||||
}
|
||||
t.Setenv("OPENROUTER_API_KEY", "")
|
||||
if _, err := LoadPipeline(root); err == nil {
|
||||
t.Fatal("empty selected credential accepted")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user