Code Diff
diff --git a/apps/provisioning/pkg/repository/git/extra.go b/apps/provisioning/pkg/repository/git/extra.go
index 1f9e9fbb40540..4515ff4aa96b6 100644
--- a/apps/provisioning/pkg/repository/git/extra.go
+++ b/apps/provisioning/pkg/repository/git/extra.go
@@ -12,11 +12,14 @@ import (
type extra struct {
decrypter repository.Decrypter
+ // allowInsecure permits http:// URLs together with a token (cleartext credentials); local/dev only.
+ allowInsecure bool
}
-func Extra(decrypter repository.Decrypter) repository.Extra {
+func Extra(decrypter repository.Decrypter, allowInsecure bool) repository.Extra {
return &extra{
- decrypter: decrypter,
+ decrypter: decrypter,
+ allowInsecure: allowInsecure,
}
}
@@ -51,5 +54,5 @@ func (e *extra) Mutate(ctx context.Context, obj runtime.Object) error {
}
func (e *extra) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
- return Validate(ctx, obj)
+ return Validate(ctx, obj, e.allowInsecure)
}
diff --git a/apps/provisioning/pkg/repository/git/validator.go b/apps/provisioning/pkg/repository/git/validator.go
index 8523f841ebca9..f32a9d4ac35ed 100644
--- a/apps/provisioning/pkg/repository/git/validator.go
+++ b/apps/provisioning/pkg/repository/git/validator.go
@@ -12,7 +12,9 @@ import (
)
// Validate validates the git repository configuration without requiring decrypted secrets.
-func Validate(_ context.Context, obj runtime.Object) field.ErrorList {
+// allowInsecure permits http:// URLs together with a token (cleartext credentials); it should
+// only be true for local/dev environments.
+func Validate(_ context.Context, obj runtime.Object, allowInsecure bool) field.ErrorList {
repo, ok := obj.(*provisioning.Repository)
if !ok {
return nil
@@ -29,20 +31,22 @@ func Validate(_ context.Context, obj runtime.Object) field.ErrorList {
}
}
- return validateGitConfig(repo, cfg)
+ return validateGitConfig(repo, cfg, allowInsecure)
}
// validateGitConfig validates the git configuration fields.
// This is extracted to be reusable by other git-based repository types (github, gitlab, bitbucket).
-func validateGitConfig(repo *provisioning.Repository, cfg *provisioning.GitRepositoryConfig) field.ErrorList {
- return ValidateGitConfigFields(repo, cfg.URL, cfg.Branch, cfg.Path)
+func validateGitConfig(repo *provisioning.Repository, cfg *provisioning.GitRepositoryConfig, allowInsecure bool) field.ErrorList {
+ return ValidateGitConfigFields(repo, cfg.URL, cfg.Branch, cfg.Path, allowInsecure)
}
// ValidateGitConfigFields validates common git configuration fields (Branch, Path, token/connection).
// This can be reused by git-based repository types (github, gitlab, bitbucket).
// The URL parameter is only used for token/connection validation logic, not for URL format validation
// (providers handle their own URL format validation).
-func ValidateGitConfigFields(repo *provisioning.Repository, url, branch, path string) field.ErrorList {
+// allowInsecure permits http:// URLs together with a token; when false, that combination is rejected
+// because it sends credentials in cleartext.
+func ValidateGitConfigFields(repo *provisioning.Repository, url, branch, path string, allowInsecure bool) field.ErrorList {
var list field.ErrorList
t := string(repo.Spec.Type)
@@ -58,6 +62,15 @@ func ValidateGitConfigFields(repo *provisioning.Repository, url, branch, path st
}
}
+ // Reject http:// together with a token: the token would travel in cleartext on every git
+ // operation. The token is a presence check (IsZero) that works without decryption.
+ // URL schemes are case-insensitive, so normalize before comparing (https:// is unaffected,
+ // since it does not have the http:// prefix).
+ if !allowInsecure && strings.HasPrefix(strings.ToLower(url), "http://") && !repo.Secure.Token.IsZero() {
+ list = append(list, field.Invalid(field.NewPath("spec", t, "url"), url,
+ "http:// is not allowed when a token is configured; use https:// to avoid sending credentials in cleartext"))
+ }
+
// Validate branch name format if a branch is provided (applies to all repository types)
if branch != "" && !IsValidGitBranchName(branch) {
list = append(list, field.Invalid(field.NewPath("spec", t, "branch"), branch, "invalid branch name"))
diff --git a/apps/provisioning/pkg/repository/git/validator_test.go b/apps/provisioning/pkg/repository/git/validator_test.go
index 7e576dce11f61..712fcdb40e1df 100644
--- a/apps/provisioning/pkg/repository/git/validator_test.go
+++ b/apps/provisioning/pkg/repository/git/validator_test.go
@@ -16,6 +16,7 @@ func TestValidate(t *testing.T) {
tests := []struct {
name string
obj runtime.Object
+ allowInsecure bool
expectedError bool
errorContains []string
}{
@@ -231,6 +232,90 @@ func TestValidate(t *testing.T) {
},
},
},
+ {
+ name: "http url with token is rejected",
+ obj: &provisioning.Repository{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-repo",
+ },
+ Spec: provisioning.RepositorySpec{
+ Type: provisioning.GitRepositoryType,
+ Git: &provisioning.GitRepositoryConfig{
+ URL: "http://localhost:3000/grafana/grafana.git",
+ Branch: "main",
+ Path: "grafana",
+ },
+ },
+ Secure: provisioning.SecureValues{
+ Token: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-token"),
+ },
+ },
+ },
+ expectedError: true,
+ errorContains: []string{"http:// is not allowed when a token is configured"},
+ },
+ {
+ name: "http url with token is allowed when insecure is permitted",
+ obj: &provisioning.Repository{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-repo",
+ },
+ Spec: provisioning.RepositorySpec{
+ Type: provisioning.GitRepositoryType,
+ Git: &provisioning.GitRepositoryConfig{
+ URL: "http://localhost:3000/grafana/grafana.git",
+ Branch: "main",
+ Path: "grafana",
+ },
+ },
+ Secure: provisioning.SecureValues{
+ Token: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-token"),
+ },
+ },
+ },
+ allowInsecure: true,
+ },
+ {
+ name: "uppercase HTTP scheme with token is rejected",
+ obj: &provisioning.Repository{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-repo",
+ },
+ Spec: provisioning.RepositorySpec{
+ Type: provisioning.GitRepositoryType,
+ Git: &provisioning.GitRepositoryConfig{
+ URL: "HTTP://localhost:3000/grafana/grafana.git",
+ Branch: "main",
+ Path: "grafana",
+ },
+ },
+ Secure: provisioning.SecureValues{
+ Token: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-token"),
+ },
+ },
+ },
+ expectedError: true,
+ errorContains: []string{"http:// is not allowed when a token is configured"},
+ },
+ {
+ name: "http url without token is allowed",
+ obj: &provisioning.Repository{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-repo",
+ },
+ Spec: provisioning.RepositorySpec{
+ Type: provisioning.GitRepositoryType,
+ Git: &provisioning.GitRepositoryConfig{
+ URL: "http://localhost:3000/grafana/grafana.git",
+ Branch: "main",
+ Path: "grafana",
+ },
+ },
+ },
+ },
{
name: "valid git repository with connection",
obj: &provisioning.Repository{
@@ -255,7 +340,7 @@ func TestValidate(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- list := Validate(context.Background(), tt.obj)
+ list := Validate(context.Background(), tt.obj, tt.allowInsecure)
if tt.expectedError {
assert.NotEmpty(t, list)
if len(tt.errorContains) > 0 {
diff --git a/apps/provisioning/pkg/repository/github/extra.go b/apps/provisioning/pkg/repository/github/extra.go
index 8bf10b0d5e561..357bdb86fd285 100644
--- a/apps/provisioning/pkg/repository/github/extra.go
+++ b/apps/provisioning/pkg/repository/github/extra.go
@@ -24,14 +24,17 @@ type extra struct {
decrypter repository.Decrypter
webhookBuilder WebhookURLBuilder
incrementalPolicy repository.IncrementalSyncPolicy
+ // allowInsecure permits http:// URLs together with a token (cleartext credentials); local/dev only.
+ allowInsecure bool
}
-func Extra(decrypter repository.Decrypter, factory *Factory, webhookBuilder WebhookURLBuilder, incrementalPolicy repository.IncrementalSyncPolicy) repository.Extra {
+func Extra(decrypter repository.Decrypter, factory *Factory, webhookBuilder WebhookURLBuilder, incrementalPolicy repository.IncrementalSyncPolicy, allowInsecure bool) repository.Extra {
return &extra{
decrypter: decrypter,
factory: factory,
webhookBuilder: webhookBuilder,
incrementalPolicy: incrementalPolicy,
+ allowInsecure: allowInsecure,
}
}
@@ -90,5 +93,5 @@ func (e *extra) Mutate(ctx context.Context, obj runtime.Object) error {
}
func (e *extra) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
- return Validate(ctx, obj)
+ return Validate(ctx, obj, e.allowInsecure)
}
diff --git a/apps/provisioning/pkg/repository/github/extra_test.go b/apps/provisioning/pkg/repository/github/extra_test.go
index b03789776df1a..dcdda8f544af0 100644
--- a/apps/provisioning/pkg/repository/github/extra_test.go
+++ b/apps/provisioning/pkg/repository/github/extra_test.go
@@ -32,7 +32,7 @@ func (m *mockSecureValues) WebhookSecret(_ context.Context) (common.RawSecureVal
}
func TestExtra_Type(t *testing.T) {
- e := github.Extra(nil, nil, nil, repository.IncrementalSyncPolicy{})
+ e := github.Extra(nil, nil, nil, repository.IncrementalSyncPolicy{}, false)
assert.Equal(t, provisioning.GitHubRepositoryType, e.Type())
}
@@ -263,7 +263,7 @@ func TestExtra_Build(t *testing.T) {
webhookBuilder := tt.setupWebhook(t, tt.repo)
factory := github.ProvideFactory()
- e := github.Extra(decrypter, factory, webhookBuilder, repository.IncrementalSyncPolicy{})
+ e := github.Extra(decrypter, factory, webhookBuilder, repository.IncrementalSyncPolicy{}, false)
result, err := e.Build(ctx, tt.repo)
@@ -393,7 +393,7 @@ func TestExtra_Mutate(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
ctx := context.Background()
- e := github.Extra(nil, nil, nil, repository.IncrementalSyncPolicy{})
+ e := github.Extra(nil, nil, nil, repository.IncrementalSyncPolicy{}, false)
err := e.Mutate(ctx, tt.obj)
diff --git a/apps/provisioning/pkg/repository/github/validator.go b/apps/provisioning/pkg/repository/github/validator.go
index 168ab15bf94b9..64a5f3e73f074 100644
--- a/apps/provisioning/pkg/repository/github/validator.go
+++ b/apps/provisioning/pkg/repository/github/validator.go
@@ -12,7 +12,9 @@ import (
)
// Validate validates the github repository configuration without requiring decrypted secrets.
-func Validate(_ context.Context, obj runtime.Object) field.ErrorList {
+// allowInsecure permits http:// URLs together with a token (cleartext credentials); it should
+// only be true for local/dev environments.
+func Validate(_ context.Context, obj runtime.Object, allowInsecure bool) field.ErrorList {
repo, ok := obj.(*provisioning.Repository)
if !ok {
return nil
@@ -49,6 +51,6 @@ func Validate(_ context.Context, obj runtime.Object) field.ErrorList {
}
// Validate git-related fields (branch, path, token/connection) using the shared git validator
- list = append(list, git.ValidateGitConfigFields(repo, gh.URL, gh.Branch, gh.Path)...)
+ list = append(list, git.ValidateGitConfigFields(repo, gh.URL, gh.Branch, gh.Path, allowInsecure)...)
return list
}
diff --git a/apps/provisioning/pkg/repository/github/validator_test.go b/apps/provisioning/pkg/repository/github/validator_test.go
index c2c6eaedf44ef..a553b3fefd099 100644
--- a/apps/provisioning/pkg/repository/github/validator_test.go
+++ b/apps/provisioning/pkg/repository/github/validator_test.go
@@ -16,6 +16,7 @@ func TestValidate(t *testing.T) {
tests := []struct {
name string
obj runtime.Object
+ allowInsecure bool
expectedError bool
errorContains []string
}{
@@ -65,7 +66,7 @@ func TestValidate(t *testing.T) {
errorContains: []string{"url"},
},
{
- name: "valid HTTP URL for local development",
+ name: "http URL with token is rejected by default",
obj: &provisioning.Repository{
ObjectMeta: metav1.ObjectMeta{
Name: "test-repo",
@@ -83,7 +84,44 @@ func TestValidate(t *testing.T) {
},
},
},
- expectedError: false,
+ expectedError: true,
+ errorContains: []string{"http:// is not allowed when a token is configured"},
+ },
+ {
+ name: "http URL with token is allowed when insecure is permitted (local development)",
+ obj: &provisioning.Repository{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-repo",
+ },
+ Spec: provisioning.RepositorySpec{
+ Type: provisioning.GitHubRepositoryType,
+ GitHub: &provisioning.GitHubRepositoryConfig{
+ URL: "http://github.com/grafana/grafana",
+ Branch: "main",
+ },
+ },
+ Secure: provisioning.SecureValues{
+ Token: common.InlineSecureValue{
+ Create: common.NewSecretValue("test-token"),
+ },
+ },
+ },
+ allowInsecure: true,
+ },
+ {
+ name: "http URL without token is allowed",
+ obj: &provisioning.Repository{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: "test-repo",
+ },
+ Spec: provisioning.RepositorySpec{
+ Type: provisioning.GitHubRepositoryType,
+ GitHub: &provisioning.GitHubRepositoryConfig{
+ URL: "http://github.com/grafana/grafana",
+ Branch: "main",
+ },
+ },
+ },
},
{
name: "valid github.com repository",
@@ -110,7 +148,7 @@ func TestValidate(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- list := Validate(context.Background(), tt.obj)
+ list := Validate(context.Background(), tt.obj, tt.allowInsecure)
if tt.expectedError {
assert.NotEmpty(t, list)
if len(tt.errorContains) > 0 {
diff --git a/conf/defaults.ini b/conf/defaults.ini
index 16cd4842161e9..cefd01bc79346 100644
--- a/conf/defaults.ini
+++ b/conf/defaults.ini
@@ -2405,6 +2405,11 @@ allowed_targets = folder
# Requires image rendering service to be configured.
allow_image_rendering = true
+# Allow http:// repository URLs together with a configured token. This sends the token in
+# cleartext on every git operation, so it is rejected by default. Intended for local/dev only
+# (it is also implicitly allowed when app_mode = development).
+allow_insecure = false
+
# The minimum sync interval that can be set for a repository. This is how often the controller
# will check if there has been any changes to the repository not propagated by a webhook.
# The minimum value is 10 seconds.
diff --git a/pkg/operators/provisioning/config.go b/pkg/operators/provisioning/config.go
index 33acdf1f40959..afecfac39e21a 100644
--- a/pkg/operators/provisioning/config.go
+++ b/p
... [truncated]