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 <james.mcshane@superorbital.io>
  • Loading branch information
jmcshane committed Oct 23, 2023
1 parent 56a7bb7 commit e9091cf
Show file tree
Hide file tree
Showing 12 changed files with 388 additions and 42 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 @@ -443,3 +445,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 @@ -111,6 +111,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 @@ -491,3 +495,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
```

21 changes: 19 additions & 2 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 @@ -1381,7 +1382,7 @@ func GenerateManifests(ctx context.Context, appPath, repoRoot, revision string,
pluginName = q.ApplicationSource.Plugin.Name
}
// if pluginName is provided it has to be `<metadata.name>-<spec.version>` or just `<metadata.name>` if plugin version is empty
targetObjs, err = runConfigManagementPluginSidecars(ctx, appPath, repoRoot, pluginName, env, q, opt.cmpTarDoneCh, opt.cmpTarExcludedGlobs)
targetObjs, err = runConfigManagementPluginSidecars(ctx, appPath, repoRoot, pluginName, env, q, q.Repo.GetGitCreds(gitCredsStore), opt.cmpTarDoneCh, opt.cmpTarExcludedGlobs)
if err != nil {
err = fmt.Errorf("plugin sidecar failed. %s", err.Error())
}
Expand Down Expand Up @@ -1882,7 +1883,7 @@ func getPluginParamEnvs(envVars []string, plugin *v1alpha1.ApplicationSourcePlug
return env, nil
}

func runConfigManagementPluginSidecars(ctx context.Context, appPath, repoPath, pluginName string, envVars *v1alpha1.Env, q *apiclient.ManifestRequest, tarDoneCh chan<- bool, tarExcludedGlobs []string) ([]*unstructured.Unstructured, error) {
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)
if err != nil {
Expand All @@ -1896,6 +1897,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
48 changes: 47 additions & 1 deletion test/e2e/custom_tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ func TestCustomToolWithGitCreds(t *testing.T) {
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy))
Expect(HealthIs(health.HealthStatusHealthy)).
And(func(app *Application) {
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.GitAskpass}")
assert.NoError(t, err)
assert.Equal(t, "argocd", output)
})
}

// make sure we can echo back the Git creds
Expand All @@ -65,6 +70,11 @@ func TestCustomToolWithGitCredsTemplate(t *testing.T) {
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy)).
And(func(app *Application) {
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.GitAskpass}")
assert.NoError(t, err)
assert.Equal(t, "argocd", output)
}).
And(func(app *Application) {
output, err := Run("", "kubectl", "-n", DeploymentNamespace(), "get", "cm", ctx.AppName(), "-o", "jsonpath={.metadata.annotations.GitUsername}")
assert.NoError(t, err)
Expand All @@ -77,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 e9091cf

Please sign in to comment.