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 4, 2024
1 parent 2f6b6ad commit c9d1c3a
Show file tree
Hide file tree
Showing 12 changed files with 202 additions and 46 deletions.
126 changes: 84 additions & 42 deletions cmpserver/apiclient/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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
2 changes: 1 addition & 1 deletion cmpserver/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ func getParametersAnnouncement(ctx context.Context, appDir string, announcements

func (s *Service) CheckPluginConfiguration(ctx context.Context, _ *empty.Empty) (*apiclient.CheckPluginConfigurationResponse, error) {
isDiscoveryConfigured := s.isDiscoveryConfigured()
response := &apiclient.CheckPluginConfigurationResponse{IsDiscoveryConfigured: isDiscoveryConfigured}
response := &apiclient.CheckPluginConfigurationResponse{IsDiscoveryConfigured: isDiscoveryConfigured, ProvideGitCreds: s.initConstants.PluginConfig.Spec.ProvideGitCreds}

return response, nil
}
Expand Down
1 change: 1 addition & 0 deletions cmpserver/plugin/plugin.proto
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ message File {
// CheckPluginConfigurationResponse contains a list of plugin configuration flags.
message CheckPluginConfigurationResponse {
bool isDiscoveryConfigured = 1;
bool provideGitCreds = 2;
}

// ConfigManagementPlugin Service
Expand Down
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 @@ -113,6 +113,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 @@ -503,3 +507,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
```

22 changes: 20 additions & 2 deletions reposerver/repository/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import (
"golang.org/x/sync/semaphore"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"

"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand Down Expand Up @@ -1457,7 +1459,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, opt.cmpUseManifestGeneratePaths)
targetObjs, err = runConfigManagementPluginSidecars(ctx, appPath, repoRoot, pluginName, env, q, q.Repo.GetGitCreds(gitCredsStore), opt.cmpTarDoneCh, opt.cmpTarExcludedGlobs, opt.cmpUseManifestGeneratePaths)
if err != nil {
err = fmt.Errorf("plugin sidecar failed. %s", err.Error())
}
Expand Down Expand Up @@ -1974,7 +1976,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, useManifestGeneratePaths bool) ([]*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, useManifestGeneratePaths bool) ([]*unstructured.Unstructured, error) {
// compute variables.
env, err := getPluginEnvs(envVars, q)
if err != nil {
Expand All @@ -1995,6 +1997,22 @@ func runConfigManagementPluginSidecars(ctx context.Context, appPath, repoPath, p
log.Debugf("common root path calculated for application %s: %s", q.AppName, rootPath)
}

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

if pluginConfigResponse.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, rootPath, 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}")
require.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}")
require.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}")
require.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}")
require.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}")
require.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 c9d1c3a

Please sign in to comment.