Skip to content

Commit

Permalink
cmp parameter to enable git creds to be shared from repo server to th…
Browse files Browse the repository at this point in the history
…e plugin

Signed-off-by: jmcshane <[email protected]>
  • Loading branch information
jmcshane committed Sep 14, 2023
1 parent db2da6b commit ea9ca11
Show file tree
Hide file tree
Showing 12 changed files with 380 additions and 54 deletions.
292 changes: 254 additions & 38 deletions cmpserver/apiclient/plugin.pb.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions cmpserver/plugin/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type PluginConfigSpec struct {
Discover Discover `json:"discover"`
Parameters Parameters `yaml:"parameters"`
PreserveFileMode bool `json:"preserveFileMode,omitempty"`
ProvideGitCreds bool `json:"provideGitCreds,omitempty"`
}

// Discover holds find and fileName
Expand Down
11 changes: 10 additions & 1 deletion cmpserver/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@ import (
"github.com/argoproj/argo-cd/v2/util/io/files"

"github.com/argoproj/gitops-engine/pkg/utils/kube"
"github.com/cyphar/filepath-securejoin"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/mattn/go-zglob"
log "github.com/sirupsen/logrus"

emptypb "google.golang.org/protobuf/types/known/emptypb"
)

// cmpTimeoutBuffer is the amount of time before the request deadline to timeout server-side work. It makes sure there's
Expand Down Expand Up @@ -438,3 +440,10 @@ func getParametersAnnouncement(ctx context.Context, appDir string, announcements
}
return repoResponse, nil
}

// GetCmpSettings shares information about the plugin configuration to the reposerver
func (s *Service) GetCmpSettings(ctx context.Context, in *emptypb.Empty) (*apiclient.CmpSettingsResponse, error) {
return &apiclient.CmpSettingsResponse{
ProvideGitCreds: s.initConstants.PluginConfig.Spec.ProvideGitCreds,
}, nil
}
9 changes: 9 additions & 0 deletions cmpserver/plugin/plugin.proto
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ option go_package = "github.com/argoproj/argo-cd/v2/cmpserver/apiclient";
package plugin;

import "github.com/argoproj/argo-cd/v2/reposerver/repository/repository.proto";
import "google/protobuf/empty.proto";

// AppStreamRequest is the request object used to send the application's
// files over a stream.
Expand Down Expand Up @@ -57,6 +58,10 @@ message File {
bytes chunk = 1;
}

message CmpSettingsResponse {
bool provideGitCreds = 1;
}

// ConfigManagementPlugin Service
service ConfigManagementPluginService {
// GenerateManifests receive a stream containing a tgz archive with all required files necessary
Expand All @@ -71,4 +76,8 @@ service ConfigManagementPluginService {
// GetParametersAnnouncement gets a list of parameter announcements for the given app
rpc GetParametersAnnouncement(stream AppStreamRequest) returns (ParametersAnnouncementResponse) {
}

// GetCmpSettings provides configuration information for the plugin
rpc GetCmpSettings(google.protobuf.Empty) returns (CmpSettingsResponse) {
}
}
30 changes: 30 additions & 0 deletions docs/operator-manual/config-management-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ spec:
# If set to `true` then the plugin receives repository files with original file mode. Dangerous since the repository
# might have executable files. Set to true only if you trust the CMP plugin authors.
preserveFileMode: false

# If set to `true` then the plugin can retrieve git credentials from the reposerver during generate. Plugin authors
# should ensure these credentials are appropriately protected during execution
provideGitCreds: false
```
!!! note
Expand Down Expand Up @@ -489,3 +493,29 @@ spec:
args: ["sample args"]
preserveFileMode: true
```
##### Provide Git Credentials
By default, the config management plugin is responsible for providing its own credentials to additional Git repositories
that may need to be accessed during manifest generation. The reposerver has these credentials available in its git creds
store, to allow the plugin to access these credentials, you can set `provideGitCreds` to `true` in the plugin spec:

!!! warning
Make sure you trust the plugin you are using. If you set `provideGitCreds` to `true` then the plugin will receive
credentials used to clone the source Git repository.

```yaml
apiVersion: argoproj.io/v1alpha1
kind: ConfigManagementPlugin
metadata:
name: pluginName
spec:
init:
command: ["sample command"]
args: ["sample args"]
generate:
command: ["sample command"]
args: ["sample args"]
provideGitCreds: true
```

37 changes: 22 additions & 15 deletions reposerver/repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import (
"golang.org/x/sync/semaphore"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
Expand Down Expand Up @@ -1839,25 +1840,17 @@ func makeJsonnetVm(appPath string, repoRoot string, sourceJsonnet v1alpha1.Appli
return vm, nil
}

func getPluginEnvs(env *v1alpha1.Env, q *apiclient.ManifestRequest, creds git.Creds) ([]string, error) {
func getPluginEnvs(env *v1alpha1.Env, q *apiclient.ManifestRequest) ([]string, error) {
envVars := env.Environ()
envVars = append(envVars, "KUBE_VERSION="+text.SemVer(q.KubeVersion))
envVars = append(envVars, "KUBE_API_VERSIONS="+strings.Join(q.ApiVersions, ","))

return getPluginParamEnvs(envVars, q.ApplicationSource.Plugin, creds)
return getPluginParamEnvs(envVars, q.ApplicationSource.Plugin)
}

// getPluginParamEnvs gets environment variables for plugin parameter announcement generation.
func getPluginParamEnvs(envVars []string, plugin *v1alpha1.ApplicationSourcePlugin, creds git.Creds) ([]string, error) {
func getPluginParamEnvs(envVars []string, plugin *v1alpha1.ApplicationSourcePlugin) ([]string, error) {
env := envVars
if creds != nil {
closer, environ, err := creds.Environ()
if err != nil {
return nil, err
}
defer func() { _ = closer.Close() }()
env = append(env, environ...)
}

parsedEnv := make(v1alpha1.Env, len(env))
for i, v := range env {
Expand Down Expand Up @@ -1886,7 +1879,7 @@ func getPluginParamEnvs(envVars []string, plugin *v1alpha1.ApplicationSourcePlug

func runConfigManagementPluginSidecars(ctx context.Context, appPath, repoPath, pluginName string, envVars *v1alpha1.Env, q *apiclient.ManifestRequest, creds git.Creds, tarDoneCh chan<- bool, tarExcludedGlobs []string) ([]*unstructured.Unstructured, error) {
// compute variables.
env, err := getPluginEnvs(envVars, q, creds)
env, err := getPluginEnvs(envVars, q)
if err != nil {
return nil, err
}
Expand All @@ -1898,6 +1891,22 @@ func runConfigManagementPluginSidecars(ctx context.Context, appPath, repoPath, p
}
defer io.Close(conn)

cmpSettingsResponse, err := cmpClient.GetCmpSettings(ctx, &emptypb.Empty{})
if err != nil {
return nil, err
}

if cmpSettingsResponse.ProvideGitCreds {
if creds != nil {
closer, environ, err := creds.Environ()
if err != nil {
return nil, err
}
defer func() { _ = closer.Close() }()
env = append(env, environ...)
}
}

// generate manifests using commands provided in plugin config file in detected cmp-server sidecar
cmpManifests, err := generateManifestsCMP(ctx, appPath, repoPath, env, cmpClient, tarDoneCh, tarExcludedGlobs)
if err != nil {
Expand Down Expand Up @@ -2128,16 +2137,14 @@ func populateKustomizeAppDetails(res *apiclient.RepoAppDetailsResponse, q *apicl
func populatePluginAppDetails(ctx context.Context, res *apiclient.RepoAppDetailsResponse, appPath string, repoPath string, q *apiclient.RepoServerAppDetailsQuery, store git.CredsStore, tarExcludedGlobs []string) error {
res.Plugin = &apiclient.PluginAppSpec{}

creds := q.Repo.GetGitCreds(store)

envVars := []string{
fmt.Sprintf("ARGOCD_APP_NAME=%s", q.AppName),
fmt.Sprintf("ARGOCD_APP_SOURCE_REPO_URL=%s", q.Repo.Repo),
fmt.Sprintf("ARGOCD_APP_SOURCE_PATH=%s", q.Source.Path),
fmt.Sprintf("ARGOCD_APP_SOURCE_TARGET_REVISION=%s", q.Source.TargetRevision),
}

env, err := getPluginParamEnvs(envVars, q.Source.Plugin, creds)
env, err := getPluginParamEnvs(envVars, q.Source.Plugin)
if err != nil {
return fmt.Errorf("failed to get env vars for plugin: %w", err)
}
Expand Down
36 changes: 36 additions & 0 deletions test/e2e/custom_tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,42 @@ func TestCustomToolWithGitCredsTemplate(t *testing.T) {
})
}

// make sure we can read the Git creds stored in a temporary file
func TestCustomToolWithSSHGitCreds(t *testing.T) {
ctx := Given(t)
// path does not matter, we ignore it
ctx.
And(func() {
go startCMPServer(t, "./testdata/cmp-gitsshcreds")
time.Sleep(1 * time.Second)
t.Setenv("ARGOCD_BINARY_NAME", "argocd")
}).
CustomCACertAdded().
// add the private repo with ssh credentials
CustomSSHKnownHostsAdded().
SSHRepoURLAdded(true).
RepoURLType(RepoURLTypeSSH).
Path("cmp-gitsshcreds").
When().
CreateApp().
Sync().
Sync().
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
And(func(app *Application) {
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.GitSSHCommand}")
assert.NoError(t, err)
assert.Regexp(t, `-i [^ ]+`, output, "test plugin expects $GIT_SSH_COMMAND to contain the option '-i <path to ssh private key>'")
}).
And(func(app *Application) {
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", Name(), "-o", "jsonpath={.metadata.annotations.GitSSHCredsFileSHA}")
assert.NoError(t, err)
assert.Regexp(t, `\w+\s+[\/\w]+`, output, "git ssh credentials file should be able to be read, hashing the contents")
})
}

// make sure we can echo back the env
func TestCustomToolWithEnv(t *testing.T) {
ctx := Given(t)
Expand Down
1 change: 1 addition & 0 deletions test/e2e/testdata/cmp-gitcreds/plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ spec:
command: [sh, -c, 'echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"GitAskpass\": \"$GIT_ASKPASS\"}}}"']
discover:
fileName: "subdir/s*.yaml"
provideGitCreds: true
1 change: 1 addition & 0 deletions test/e2e/testdata/cmp-gitcredstemplate/plugin.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ spec:
command: [sh, -c, 'echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"GitAskpass\": \"$GIT_ASKPASS\", \"GitUsername\": \"$GIT_USERNAME\", \"GitPassword\": \"$GIT_PASSWORD\"}}}"']
discover:
fileName: "subdir/s*.yaml"
provideGitCreds: true
5 changes: 5 additions & 0 deletions test/e2e/testdata/cmp-gitsshcreds/generate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#!/bin/bash

FILE=$(echo "$GIT_SSH_COMMAND" | grep -oP '\-i \K[/\w]+')
GIT_SSH_CRED_FILE_SHA=$(sha256sum ${FILE})
echo "{\"kind\": \"ConfigMap\", \"apiVersion\": \"v1\", \"metadata\": { \"name\": \"$ARGOCD_APP_NAME\", \"namespace\": \"$ARGOCD_APP_NAMESPACE\", \"annotations\": {\"GitSSHCommand\":\"$GIT_SSH_COMMAND\", \"GitSSHCredsFileSHA\":\"$GIT_SSH_CRED_FILE_SHA\"}}}"
11 changes: 11 additions & 0 deletions test/e2e/testdata/cmp-gitsshcreds/plugin.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
apiVersion: argoproj.io/v1alpha1
kind: ConfigManagementPlugin
metadata:
name: cmp-gitcredstemplate
spec:
version: v1.0
generate:
command: ["sh", "generate.sh"]
discover:
fileName: "subdir/s*.yaml"
provideGitCreds: true
Empty file.

0 comments on commit ea9ca11

Please sign in to comment.