diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 120000 index 00000000..2cd137d7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1 @@ +/nix/store/davrh460p36m2bpnchpfb2majib7cag0-pre-commit-config.json \ No newline at end of file diff --git a/plugins/helm/helm.go b/plugins/helm/helm.go new file mode 100644 index 00000000..9c799901 --- /dev/null +++ b/plugins/helm/helm.go @@ -0,0 +1,24 @@ +package helm + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/needsauth" + "github.com/1Password/shell-plugins/sdk/schema" + "github.com/1Password/shell-plugins/sdk/schema/credname" +) + +func HelmCLI() schema.Executable { + return schema.Executable{ + Name: "Helm", + Runs: []string{"helm"}, + DocsURL: sdk.URL("https://helm.sh/docs/"), + NeedsAuth: needsauth.IfAll( + needsauth.NotForHelpOrVersion(), + needsauth.NotWithoutArgs(), + ), + Uses: []schema.CredentialUsage{ + {Name: sdk.CredentialName("Kubeconfig")}, + {Name: credname.SecretKey, Plugin: "sops", Optional: true}, + }, + } +} diff --git a/plugins/helm/helm_credentials.go b/plugins/helm/helm_credentials.go new file mode 100644 index 00000000..92093ca1 --- /dev/null +++ b/plugins/helm/helm_credentials.go @@ -0,0 +1,41 @@ +package helm + +import ( + "context" + "encoding/base64" + + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/importer" + "github.com/1Password/shell-plugins/sdk/schema" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +func Kubeconfig() schema.CredentialType { + return schema.CredentialType{ + Name: sdk.CredentialName("Kubeconfig"), + DocsURL: sdk.URL("https://helm.sh/docs/"), + Fields: []schema.CredentialField{ + { + Name: fieldname.Credential, + MarkdownDescription: "Base64-encoded kubeconfig YAML file contents.", + Secret: true, + }, + }, + DefaultProvisioner: &helmKubeconfigProvisioner{}, + Importer: importer.TryAll( + TryKubeconfigFile(), + ), + } +} + +func TryKubeconfigFile() sdk.Importer { + return importer.TryFile("~/.kube/config", func(ctx context.Context, contents importer.FileContents, in sdk.ImportInput, out *sdk.ImportAttempt) { + encoded := base64.StdEncoding.EncodeToString(contents) + out.AddCandidate(sdk.ImportCandidate{ + Fields: map[sdk.FieldName]string{ + fieldname.Credential: encoded, + }, + NameHint: importer.SanitizeNameHint("default"), + }) + }) +} diff --git a/plugins/helm/helm_credentials_test.go b/plugins/helm/helm_credentials_test.go new file mode 100644 index 00000000..9139b50a --- /dev/null +++ b/plugins/helm/helm_credentials_test.go @@ -0,0 +1,49 @@ +package helm + +import ( + "encoding/base64" + "testing" + + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/plugintest" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +func TestKubeconfigProvisioner(t *testing.T) { + rawConfig := plugintest.LoadFixture(t, "config") + encodedConfig := base64.StdEncoding.EncodeToString([]byte(rawConfig)) + + plugintest.TestProvisioner(t, Kubeconfig().DefaultProvisioner, map[string]plugintest.ProvisionCase{ + "kubeconfig": { + ItemFields: map[sdk.FieldName]string{ + fieldname.Credential: encodedConfig, + }, + ExpectedOutput: sdk.ProvisionOutput{ + Environment: map[string]string{ + "KUBECONFIG": "/tmp/config", + }, + }, + }, + }) +} + +func TestKubeconfigImporter(t *testing.T) { + rawConfig := plugintest.LoadFixture(t, "config") + encodedConfig := base64.StdEncoding.EncodeToString([]byte(rawConfig)) + + plugintest.TestImporter(t, Kubeconfig().Importer, map[string]plugintest.ImportCase{ + "kubeconfig file": { + Files: map[string]string{ + "~/.kube/config": rawConfig, + }, + ExpectedCandidates: []sdk.ImportCandidate{ + { + Fields: map[sdk.FieldName]string{ + fieldname.Credential: encodedConfig, + }, + NameHint: "", + }, + }, + }, + }) +} diff --git a/plugins/helm/helmfile.go b/plugins/helm/helmfile.go new file mode 100644 index 00000000..7b2f2119 --- /dev/null +++ b/plugins/helm/helmfile.go @@ -0,0 +1,24 @@ +package helm + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/needsauth" + "github.com/1Password/shell-plugins/sdk/schema" + "github.com/1Password/shell-plugins/sdk/schema/credname" +) + +func HelmfileCLI() schema.Executable { + return schema.Executable{ + Name: "Helmfile", + Runs: []string{"helmfile"}, + DocsURL: sdk.URL("https://github.com/helmfile/helmfile"), + NeedsAuth: needsauth.IfAll( + needsauth.NotForHelpOrVersion(), + needsauth.NotWithoutArgs(), + ), + Uses: []schema.CredentialUsage{ + {Name: sdk.CredentialName("Kubeconfig")}, + {Name: credname.SecretKey, Plugin: "sops", Optional: true}, + }, + } +} diff --git a/plugins/helm/plugin.go b/plugins/helm/plugin.go new file mode 100644 index 00000000..d931f18f --- /dev/null +++ b/plugins/helm/plugin.go @@ -0,0 +1,23 @@ +package helm + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/schema" +) + +func New() schema.Plugin { + return schema.Plugin{ + Name: "helm", + Platform: schema.PlatformInfo{ + Name: "Helm", + Homepage: sdk.URL("https://helm.sh"), + }, + Credentials: []schema.CredentialType{ + Kubeconfig(), + }, + Executables: []schema.Executable{ + HelmCLI(), + HelmfileCLI(), + }, + } +} diff --git a/plugins/helm/provisioner.go b/plugins/helm/provisioner.go new file mode 100644 index 00000000..573d5741 --- /dev/null +++ b/plugins/helm/provisioner.go @@ -0,0 +1,42 @@ +package helm + +import ( + "context" + "encoding/base64" + "os" + "path/filepath" + + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +type helmKubeconfigProvisioner struct{} + +func (p *helmKubeconfigProvisioner) Description() string { + return "Provision kubeconfig file for Helm" +} + +func (p *helmKubeconfigProvisioner) Provision(ctx context.Context, in sdk.ProvisionInput, out *sdk.ProvisionOutput) { + // Decode base64 kubeconfig + encoded := in.ItemFields[fieldname.Credential] + decoded, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + out.AddError(err) + return + } + + // Write kubeconfig as a real file (not via out.AddSecretFile which creates a FIFO). + // Helm reads the kubeconfig multiple times, and FIFOs block on the second read. + configPath := filepath.Join(in.TempDir, "config") + if err := os.WriteFile(configPath, decoded, 0600); err != nil { + out.AddError(err) + return + } + out.AddEnvVar("KUBECONFIG", configPath) +} + +func (p *helmKubeconfigProvisioner) Deprovision(ctx context.Context, in sdk.DeprovisionInput, out *sdk.DeprovisionOutput) { + // Remove kubeconfig written directly to disk + configPath := filepath.Join(in.TempDir, "config") + os.Remove(configPath) +} diff --git a/plugins/helm/test-fixtures/config b/plugins/helm/test-fixtures/config new file mode 100644 index 00000000..10f840aa --- /dev/null +++ b/plugins/helm/test-fixtures/config @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Config +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJlRENDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUzTURBeU16TTJOREV3SGhjTk1qTXhNVEU1TVRBeU56SXhXaGNOTXpNeE1URTJNVEF5TnpJeApXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUzTURBeU16TTJOREV3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFUMUZiY29ycjhBQ1NkcmNqQk5xQ3JSa0M2Y2tMYmZUQ0NSSUN2UndhN2IKUjZiNUxRZHZuOXZVYkhiVHN4Z3FJQ2pYYjZRTEtIRTRrQXlQQ3BDNnBPUEpvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVWVlUllndjZBbGFCNVc0dDV0Rm9ZCm9OSHVkVDB3Q2dZSUtvWkl6ajBFQXdJRFNRQXdSZ0loQUk0TlQyR3hxNHYwZExHRlFkNkdtbHdaZ0dBckVZRWoKL1FkeWZsOU12QTZyQWlFQWljVjdiVzlqcVJwNjh4cHdJeDlYak9OeUt2V3dZMVdQVWYzQU1kQTd6T2s9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + server: https://10.0.0.1:6443 + name: default +contexts: +- context: + cluster: default + user: default + name: default +current-context: default +preferences: {} +users: +- name: default + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrVENDQVRlZ0F3SUJBZ0lJVHVNdTNtNE8waTh3Q2dZSUtvWkl6ajBFQXdJd0l6RWhNQjhHQTFVRUF3d1kKYXpOekxXTnNhV1Z1ZEMxallVQXhOekF3TWpNek5qUXhNQjRYRFRJek1URXhPVEV3TWpjeU1Wb1hEVEkwTVRFeApPREV3TWpjeU1Wb3dNREVYTUJVR0ExVUVDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGVEFUQmdOVkJBTVRESE41CmMzUmxiVHBoWkcxcGJqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJNL0hvRHZUZmg2dEtoVVUKM3lHZHBrU05EWVhGWjBYYjVVMHRKRzh4TlFiQmEzaDhRQjJMMVN0MDVNRlQ2dEhxUWpsUkJ6cG1qRnJ2L1haQQp1RUl4WmRlalNEQkdNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmCkJnTlZIU01FR0RBV2dCUmYwYnh0NTVSdlE4M3pxMGxrdnRXU28zYUNDREFLQmdncWhrak9QUVFEQWdOSUFEQkYKQWlFQXo1K2txTWJpMjJKMW1LNGFldlVYUVFGVm9hSXFWRFBzTjZHcW1iZGxaRWtDSUJqeWVxc0tkL0VxdFBXbgpaSXRCL21STC9WdDRHRUhJbGpUckxLNytlN2ZOCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkekNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdFkyeHAKWlc1MExXTmhRREUzTURBeU16TTJOREV3SGhjTk1qTXhNVEU1TVRBeU56SXhXaGNOTXpNeE1URTJNVEF5TnpJeApXakFqTVNFd0h3WURWUVFEREJock0zTXRZMnhwWlc1MExXTmhRREUzTURBeU16TTJOREV3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFSMU9ucCsxUnpLYVVVN0lJNTVhTXZJRUFXeE9udmNqd0E0NnRZSDQ3amkKb0dIVFViTHJXc3ZxMHFZeEpJZlkzRmdZekZYMVlkLzhBbVJxWGJrTldkUW9vMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVVg5RzhiZWVVYjBQTjg2dEpaTC9WCmtxTjJnZ2d3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUloQUxoSER3UlllVXBoS0ZSOURKZ2EycjlLRlBmQ2dLSnUKd0JOK2ptOW4wdm1YQWlBenM5Q0hDN1F5aElFSnNmVjVLaGF1MHhqQ1pYdEJpblQ5aEt4d0RGMTJGUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IUUNBUUVFSUFTbE5FV3lPRWRuSWxYVWpRdG1Pc1cwWjdwYWxpTUlKdENMcmhKSGZRYlBvQWNHQlN1QkJBQUsKb1VRRFFnQUV6OGVnTzlOK0hxMHFGUlRmSVoybVJJME5oY1ZuUmR2bFRTMGtieksxQnNGcmVIeEFIWXZWSzNUawp3VlBxMGVwQ09WRUhPbWFNV3UvOWRrQzRRakZsMXc9PQotLS0tLUVORCBFQyBQUklWQVRFIEtFWS0tLS0tCg== diff --git a/plugins/kubernetes/k9s.go b/plugins/kubernetes/k9s.go new file mode 100644 index 00000000..6ec7f942 --- /dev/null +++ b/plugins/kubernetes/k9s.go @@ -0,0 +1,23 @@ +package kubernetes + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/needsauth" + "github.com/1Password/shell-plugins/sdk/schema" +) + +func K9sCLI() schema.Executable { + return schema.Executable{ + Name: "k9s", + Runs: []string{"k9s"}, + DocsURL: sdk.URL("https://k9scli.io/"), + NeedsAuth: needsauth.IfAll( + needsauth.NotForHelpOrVersion(), + ), + Uses: []schema.CredentialUsage{ + { + Name: sdk.CredentialName("Kubeconfig"), + }, + }, + } +} diff --git a/plugins/kubernetes/kubeconfig.go b/plugins/kubernetes/kubeconfig.go new file mode 100644 index 00000000..279447b6 --- /dev/null +++ b/plugins/kubernetes/kubeconfig.go @@ -0,0 +1,56 @@ +package kubernetes + +import ( + "context" + "encoding/base64" + + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/importer" + "github.com/1Password/shell-plugins/sdk/provision" + "github.com/1Password/shell-plugins/sdk/schema" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +func Kubeconfig() schema.CredentialType { + return schema.CredentialType{ + Name: sdk.CredentialName("Kubeconfig"), + DocsURL: sdk.URL("https://kubernetes.io/docs/concepts/configuration/organize-cluster-access-kubeconfig/"), + Fields: []schema.CredentialField{ + { + Name: fieldname.Credential, + MarkdownDescription: "Base64-encoded kubeconfig YAML file contents.", + Secret: true, + }, + }, + DefaultProvisioner: provision.TempFile( + kubeconfigFileContents, + provision.Filename("config"), + provision.SetPathAsEnvVar("KUBECONFIG"), + ), + Importer: importer.TryAll( + TryKubeconfigFile(), + ), + } +} + +func kubeconfigFileContents(in sdk.ProvisionInput) ([]byte, error) { + encoded := in.ItemFields[fieldname.Credential] + decoded, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return nil, err + } + return decoded, nil +} + +func TryKubeconfigFile() sdk.Importer { + return importer.TryFile("~/.kube/config", func(ctx context.Context, contents importer.FileContents, in sdk.ImportInput, out *sdk.ImportAttempt) { + // Import existing kubeconfig as base64-encoded + encoded := base64.StdEncoding.EncodeToString(contents) + out.AddCandidate(sdk.ImportCandidate{ + Fields: map[sdk.FieldName]string{ + fieldname.Credential: encoded, + }, + NameHint: importer.SanitizeNameHint("default"), + }) + }) +} diff --git a/plugins/kubernetes/kubeconfig_test.go b/plugins/kubernetes/kubeconfig_test.go new file mode 100644 index 00000000..d17aca40 --- /dev/null +++ b/plugins/kubernetes/kubeconfig_test.go @@ -0,0 +1,54 @@ +package kubernetes + +import ( + "encoding/base64" + "testing" + + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/plugintest" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +func TestKubeconfigProvisioner(t *testing.T) { + rawConfig := plugintest.LoadFixture(t, "config") + encodedConfig := base64.StdEncoding.EncodeToString([]byte(rawConfig)) + + plugintest.TestProvisioner(t, Kubeconfig().DefaultProvisioner, map[string]plugintest.ProvisionCase{ + "default": { + ItemFields: map[sdk.FieldName]string{ + fieldname.Credential: encodedConfig, + }, + ExpectedOutput: sdk.ProvisionOutput{ + Environment: map[string]string{ + "KUBECONFIG": "/tmp/config", + }, + Files: map[string]sdk.OutputFile{ + "/tmp/config": { + Contents: []byte(rawConfig), + }, + }, + }, + }, + }) +} + +func TestKubeconfigImporter(t *testing.T) { + rawConfig := plugintest.LoadFixture(t, "config") + encodedConfig := base64.StdEncoding.EncodeToString([]byte(rawConfig)) + + plugintest.TestImporter(t, Kubeconfig().Importer, map[string]plugintest.ImportCase{ + "kubeconfig file": { + Files: map[string]string{ + "~/.kube/config": rawConfig, + }, + ExpectedCandidates: []sdk.ImportCandidate{ + { + Fields: map[sdk.FieldName]string{ + fieldname.Credential: encodedConfig, + }, + NameHint: "", + }, + }, + }, + }) +} diff --git a/plugins/kubernetes/kubectl.go b/plugins/kubernetes/kubectl.go new file mode 100644 index 00000000..f579e9f9 --- /dev/null +++ b/plugins/kubernetes/kubectl.go @@ -0,0 +1,24 @@ +package kubernetes + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/needsauth" + "github.com/1Password/shell-plugins/sdk/schema" +) + +func KubectlCLI() schema.Executable { + return schema.Executable{ + Name: "kubectl", + Runs: []string{"kubectl"}, + DocsURL: sdk.URL("https://kubernetes.io/docs/reference/kubectl/"), + NeedsAuth: needsauth.IfAll( + needsauth.NotForHelpOrVersion(), + needsauth.NotWithoutArgs(), + ), + Uses: []schema.CredentialUsage{ + { + Name: sdk.CredentialName("Kubeconfig"), + }, + }, + } +} diff --git a/plugins/kubernetes/plugin.go b/plugins/kubernetes/plugin.go new file mode 100644 index 00000000..648e6f9f --- /dev/null +++ b/plugins/kubernetes/plugin.go @@ -0,0 +1,24 @@ +package kubernetes + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/schema" +) + +func New() schema.Plugin { + return schema.Plugin{ + Name: "kubernetes", + Platform: schema.PlatformInfo{ + Name: "Kubernetes", + Homepage: sdk.URL("https://kubernetes.io"), + }, + Credentials: []schema.CredentialType{ + Kubeconfig(), + }, + Executables: []schema.Executable{ + KubectlCLI(), + SternCLI(), + K9sCLI(), + }, + } +} diff --git a/plugins/kubernetes/stern.go b/plugins/kubernetes/stern.go new file mode 100644 index 00000000..07ccb99e --- /dev/null +++ b/plugins/kubernetes/stern.go @@ -0,0 +1,23 @@ +package kubernetes + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/needsauth" + "github.com/1Password/shell-plugins/sdk/schema" +) + +func SternCLI() schema.Executable { + return schema.Executable{ + Name: "stern", + Runs: []string{"stern"}, + DocsURL: sdk.URL("https://github.com/stern/stern"), + NeedsAuth: needsauth.IfAll( + needsauth.NotForHelpOrVersion(), + ), + Uses: []schema.CredentialUsage{ + { + Name: sdk.CredentialName("Kubeconfig"), + }, + }, + } +} diff --git a/plugins/kubernetes/test-fixtures/config b/plugins/kubernetes/test-fixtures/config new file mode 100644 index 00000000..10f840aa --- /dev/null +++ b/plugins/kubernetes/test-fixtures/config @@ -0,0 +1,19 @@ +apiVersion: v1 +kind: Config +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJlRENDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUzTURBeU16TTJOREV3SGhjTk1qTXhNVEU1TVRBeU56SXhXaGNOTXpNeE1URTJNVEF5TnpJeApXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUzTURBeU16TTJOREV3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFUMUZiY29ycjhBQ1NkcmNqQk5xQ3JSa0M2Y2tMYmZUQ0NSSUN2UndhN2IKUjZiNUxRZHZuOXZVYkhiVHN4Z3FJQ2pYYjZRTEtIRTRrQXlQQ3BDNnBPUEpvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVWVlUllndjZBbGFCNVc0dDV0Rm9ZCm9OSHVkVDB3Q2dZSUtvWkl6ajBFQXdJRFNRQXdSZ0loQUk0TlQyR3hxNHYwZExHRlFkNkdtbHdaZ0dBckVZRWoKL1FkeWZsOU12QTZyQWlFQWljVjdiVzlqcVJwNjh4cHdJeDlYak9OeUt2V3dZMVdQVWYzQU1kQTd6T2s9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + server: https://10.0.0.1:6443 + name: default +contexts: +- context: + cluster: default + user: default + name: default +current-context: default +preferences: {} +users: +- name: default + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJrVENDQVRlZ0F3SUJBZ0lJVHVNdTNtNE8waTh3Q2dZSUtvWkl6ajBFQXdJd0l6RWhNQjhHQTFVRUF3d1kKYXpOekxXTnNhV1Z1ZEMxallVQXhOekF3TWpNek5qUXhNQjRYRFRJek1URXhPVEV3TWpjeU1Wb1hEVEkwTVRFeApPREV3TWpjeU1Wb3dNREVYTUJVR0ExVUVDaE1PYzNsemRHVnRPbTFoYzNSbGNuTXhGVEFUQmdOVkJBTVRESE41CmMzUmxiVHBoWkcxcGJqQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJNL0hvRHZUZmg2dEtoVVUKM3lHZHBrU05EWVhGWjBYYjVVMHRKRzh4TlFiQmEzaDhRQjJMMVN0MDVNRlQ2dEhxUWpsUkJ6cG1qRnJ2L1haQQp1RUl4WmRlalNEQkdNQTRHQTFVZER3RUIvd1FFQXdJRm9EQVRCZ05WSFNVRUREQUtCZ2dyQmdFRkJRY0RBakFmCkJnTlZIU01FR0RBV2dCUmYwYnh0NTVSdlE4M3pxMGxrdnRXU28zYUNDREFLQmdncWhrak9QUVFEQWdOSUFEQkYKQWlFQXo1K2txTWJpMjJKMW1LNGFldlVYUVFGVm9hSXFWRFBzTjZHcW1iZGxaRWtDSUJqeWVxc0tkL0VxdFBXbgpaSXRCL21STC9WdDRHRUhJbGpUckxLNytlN2ZOCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkekNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdFkyeHAKWlc1MExXTmhRREUzTURBeU16TTJOREV3SGhjTk1qTXhNVEU1TVRBeU56SXhXaGNOTXpNeE1URTJNVEF5TnpJeApXakFqTVNFd0h3WURWUVFEREJock0zTXRZMnhwWlc1MExXTmhRREUzTURBeU16TTJOREV3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFSMU9ucCsxUnpLYVVVN0lJNTVhTXZJRUFXeE9udmNqd0E0NnRZSDQ3amkKb0dIVFViTHJXc3ZxMHFZeEpJZlkzRmdZekZYMVlkLzhBbVJxWGJrTldkUW9vMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVVg5RzhiZWVVYjBQTjg2dEpaTC9WCmtxTjJnZ2d3Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUloQUxoSER3UlllVXBoS0ZSOURKZ2EycjlLRlBmQ2dLSnUKd0JOK2ptOW4wdm1YQWlBenM5Q0hDN1F5aElFSnNmVjVLaGF1MHhqQ1pYdEJpblQ5aEt4d0RGMTJGUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + client-key-data: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IUUNBUUVFSUFTbE5FV3lPRWRuSWxYVWpRdG1Pc1cwWjdwYWxpTUlKdENMcmhKSGZRYlBvQWNHQlN1QkJBQUsKb1VRRFFnQUV6OGVnTzlOK0hxMHFGUlRmSVoybVJJME5oY1ZuUmR2bFRTMGtieksxQnNGcmVIeEFIWXZWSzNUawp3VlBxMGVwQ09WRUhPbWFNV3UvOWRrQzRRakZsMXc9PQotLS0tLUVORCBFQyBQUklWQVRFIEtFWS0tLS0tCg== diff --git a/plugins/plugins.go b/plugins/plugins.go new file mode 100644 index 00000000..07573ba7 --- /dev/null +++ b/plugins/plugins.go @@ -0,0 +1,137 @@ +package plugins + +// This file gets auto-generated by the "make registry" command, so should not be edited by hand. + +import ( + "github.com/1Password/shell-plugins/plugins/akamai" + "github.com/1Password/shell-plugins/plugins/argocd" + "github.com/1Password/shell-plugins/plugins/atlas" + "github.com/1Password/shell-plugins/plugins/aws" + "github.com/1Password/shell-plugins/plugins/axiom" + "github.com/1Password/shell-plugins/plugins/binance" + "github.com/1Password/shell-plugins/plugins/cachix" + "github.com/1Password/shell-plugins/plugins/cargo" + "github.com/1Password/shell-plugins/plugins/circleci" + "github.com/1Password/shell-plugins/plugins/civo" + "github.com/1Password/shell-plugins/plugins/confluent" + "github.com/1Password/shell-plugins/plugins/crowdin" + "github.com/1Password/shell-plugins/plugins/databricks" + "github.com/1Password/shell-plugins/plugins/datadog" + "github.com/1Password/shell-plugins/plugins/digitalocean" + "github.com/1Password/shell-plugins/plugins/fastly" + "github.com/1Password/shell-plugins/plugins/flyctl" + "github.com/1Password/shell-plugins/plugins/fossa" + "github.com/1Password/shell-plugins/plugins/gitea" + "github.com/1Password/shell-plugins/plugins/github" + "github.com/1Password/shell-plugins/plugins/gitlab" + "github.com/1Password/shell-plugins/plugins/hcloud" + "github.com/1Password/shell-plugins/plugins/helm" + "github.com/1Password/shell-plugins/plugins/heroku" + "github.com/1Password/shell-plugins/plugins/homebrew" + "github.com/1Password/shell-plugins/plugins/huggingface" + "github.com/1Password/shell-plugins/plugins/influxdb" + "github.com/1Password/shell-plugins/plugins/kaggle" + "github.com/1Password/shell-plugins/plugins/kubernetes" + "github.com/1Password/shell-plugins/plugins/lacework" + "github.com/1Password/shell-plugins/plugins/laravelforge" + "github.com/1Password/shell-plugins/plugins/laravelvapor" + "github.com/1Password/shell-plugins/plugins/linode" + "github.com/1Password/shell-plugins/plugins/localstack" + "github.com/1Password/shell-plugins/plugins/mysql" + "github.com/1Password/shell-plugins/plugins/ngrok" + "github.com/1Password/shell-plugins/plugins/ohdear" + "github.com/1Password/shell-plugins/plugins/okta" + "github.com/1Password/shell-plugins/plugins/openai" + "github.com/1Password/shell-plugins/plugins/pipedream" + "github.com/1Password/shell-plugins/plugins/postgresql" + "github.com/1Password/shell-plugins/plugins/pulumi" + "github.com/1Password/shell-plugins/plugins/readme" + "github.com/1Password/shell-plugins/plugins/scaleway" + "github.com/1Password/shell-plugins/plugins/sentry" + "github.com/1Password/shell-plugins/plugins/snowflake" + "github.com/1Password/shell-plugins/plugins/snyk" + "github.com/1Password/shell-plugins/plugins/sops" + "github.com/1Password/shell-plugins/plugins/sourcegraph" + "github.com/1Password/shell-plugins/plugins/stripe" + "github.com/1Password/shell-plugins/plugins/terraform" + "github.com/1Password/shell-plugins/plugins/todoist" + "github.com/1Password/shell-plugins/plugins/treasuredata" + "github.com/1Password/shell-plugins/plugins/tugboat" + "github.com/1Password/shell-plugins/plugins/twilio" + "github.com/1Password/shell-plugins/plugins/upstash" + "github.com/1Password/shell-plugins/plugins/vault" + "github.com/1Password/shell-plugins/plugins/vercel" + "github.com/1Password/shell-plugins/plugins/vertica" + "github.com/1Password/shell-plugins/plugins/vultr" + "github.com/1Password/shell-plugins/plugins/wrangler" + "github.com/1Password/shell-plugins/plugins/yugabytedb" + "github.com/1Password/shell-plugins/plugins/zapier" + "github.com/1Password/shell-plugins/plugins/zendesk" +) + +func init() { + Register(akamai.New()) + Register(argocd.New()) + Register(atlas.New()) + Register(aws.New()) + Register(axiom.New()) + Register(binance.New()) + Register(cachix.New()) + Register(cargo.New()) + Register(circleci.New()) + Register(civo.New()) + Register(confluent.New()) + Register(crowdin.New()) + Register(databricks.New()) + Register(datadog.New()) + Register(digitalocean.New()) + Register(fastly.New()) + Register(flyctl.New()) + Register(fossa.New()) + Register(gitea.New()) + Register(github.New()) + Register(gitlab.New()) + Register(hcloud.New()) + Register(helm.New()) + Register(heroku.New()) + Register(homebrew.New()) + Register(huggingface.New()) + Register(influxdb.New()) + Register(kaggle.New()) + Register(kubernetes.New()) + Register(lacework.New()) + Register(laravelforge.New()) + Register(laravelvapor.New()) + Register(linode.New()) + Register(localstack.New()) + Register(mysql.New()) + Register(ngrok.New()) + Register(ohdear.New()) + Register(okta.New()) + Register(openai.New()) + Register(pipedream.New()) + Register(postgresql.New()) + Register(pulumi.New()) + Register(readme.New()) + Register(scaleway.New()) + Register(sentry.New()) + Register(snowflake.New()) + Register(snyk.New()) + Register(sops.New()) + Register(sourcegraph.New()) + Register(stripe.New()) + Register(terraform.New()) + Register(todoist.New()) + Register(treasuredata.New()) + Register(tugboat.New()) + Register(twilio.New()) + Register(upstash.New()) + Register(vault.New()) + Register(vercel.New()) + Register(vertica.New()) + Register(vultr.New()) + Register(wrangler.New()) + Register(yugabytedb.New()) + Register(zapier.New()) + Register(zendesk.New()) +} diff --git a/plugins/sops/age_secret_key.go b/plugins/sops/age_secret_key.go new file mode 100644 index 00000000..7c755561 --- /dev/null +++ b/plugins/sops/age_secret_key.go @@ -0,0 +1,40 @@ +package sops + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/importer" + "github.com/1Password/shell-plugins/sdk/provision" + "github.com/1Password/shell-plugins/sdk/schema" + "github.com/1Password/shell-plugins/sdk/schema/credname" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +func AgeSecretKey() schema.CredentialType { + return schema.CredentialType{ + Name: credname.SecretKey, + DocsURL: sdk.URL("https://github.com/getsops/sops#encrypting-using-age"), + ManagementURL: nil, + Fields: []schema.CredentialField{ + { + Name: fieldname.PrivateKey, + MarkdownDescription: "Age secret key used by SOPS for encryption and decryption.", + Secret: true, + Composition: &schema.ValueComposition{ + Prefix: "AGE-SECRET-KEY-", + Charset: schema.Charset{ + Uppercase: true, + Digits: true, + }, + }, + }, + }, + DefaultProvisioner: provision.EnvVars(defaultEnvVarMapping), + Importer: importer.TryAll( + importer.TryEnvVarPair(defaultEnvVarMapping), + ), + } +} + +var defaultEnvVarMapping = map[string]sdk.FieldName{ + "SOPS_AGE_KEY": fieldname.PrivateKey, +} diff --git a/plugins/sops/age_secret_key_test.go b/plugins/sops/age_secret_key_test.go new file mode 100644 index 00000000..506aa53e --- /dev/null +++ b/plugins/sops/age_secret_key_test.go @@ -0,0 +1,41 @@ +package sops + +import ( + "testing" + + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/plugintest" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +func TestAgeSecretKeyImporter(t *testing.T) { + plugintest.TestImporter(t, AgeSecretKey().Importer, map[string]plugintest.ImportCase{ + "environment": { + Environment: map[string]string{ + "SOPS_AGE_KEY": "AGE-SECRET-KEY-1QFWENTHXAAPACFPMXHQCREP64GJE5YTHXLX0RPFSXRSPDJGCR0SSWYNX3D", + }, + ExpectedCandidates: []sdk.ImportCandidate{ + { + Fields: map[sdk.FieldName]string{ + fieldname.PrivateKey: "AGE-SECRET-KEY-1QFWENTHXAAPACFPMXHQCREP64GJE5YTHXLX0RPFSXRSPDJGCR0SSWYNX3D", + }, + }, + }, + }, + }) +} + +func TestAgeSecretKeyProvisioner(t *testing.T) { + plugintest.TestProvisioner(t, AgeSecretKey().DefaultProvisioner, map[string]plugintest.ProvisionCase{ + "default": { + ItemFields: map[sdk.FieldName]string{ + fieldname.PrivateKey: "AGE-SECRET-KEY-1QFWENTHXAAPACFPMXHQCREP64GJE5YTHXLX0RPFSXRSPDJGCR0SSWYNX3D", + }, + ExpectedOutput: sdk.ProvisionOutput{ + Environment: map[string]string{ + "SOPS_AGE_KEY": "AGE-SECRET-KEY-1QFWENTHXAAPACFPMXHQCREP64GJE5YTHXLX0RPFSXRSPDJGCR0SSWYNX3D", + }, + }, + }, + }) +} diff --git a/plugins/sops/helm.go b/plugins/sops/helm.go new file mode 100644 index 00000000..e783d02c --- /dev/null +++ b/plugins/sops/helm.go @@ -0,0 +1,32 @@ +package sops + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/needsauth" + "github.com/1Password/shell-plugins/sdk/schema" + "github.com/1Password/shell-plugins/sdk/schema/credname" +) + +func HelmCLI() schema.Executable { + return schema.Executable{ + Name: "Helm with SOPS Secrets and Kubernetes", + Runs: []string{"helm"}, + DocsURL: sdk.URL("https://github.com/jkroepke/helm-secrets"), + NeedsAuth: needsauth.IfAll( + needsauth.NotForHelpOrVersion(), + needsauth.NotWithoutArgs(), + ), + Uses: []schema.CredentialUsage{ + { + Name: sdk.CredentialName("Kubeconfig"), + Plugin: "kubernetes", + // Kubeconfig - provisions KUBECONFIG env var pointing to temp file + }, + { + Name: credname.SecretKey, + Optional: true, + // SOPS age key - provisions SOPS_AGE_KEY env var (optional, only for helm-secrets) + }, + }, + } +} diff --git a/plugins/sops/helmfile.go b/plugins/sops/helmfile.go new file mode 100644 index 00000000..cb9aed37 --- /dev/null +++ b/plugins/sops/helmfile.go @@ -0,0 +1,25 @@ +package sops + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/needsauth" + "github.com/1Password/shell-plugins/sdk/schema" + "github.com/1Password/shell-plugins/sdk/schema/credname" +) + +func HelmfileCLI() schema.Executable { + return schema.Executable{ + Name: "Helmfile with SOPS Secrets", + Runs: []string{"helmfile"}, + DocsURL: sdk.URL("https://github.com/helmfile/helmfile"), + NeedsAuth: needsauth.IfAll( + needsauth.NotForHelpOrVersion(), + needsauth.NotWithoutArgs(), + ), + Uses: []schema.CredentialUsage{ + { + Name: credname.SecretKey, + }, + }, + } +} diff --git a/plugins/sops/plugin.go b/plugins/sops/plugin.go new file mode 100644 index 00000000..f4046cb3 --- /dev/null +++ b/plugins/sops/plugin.go @@ -0,0 +1,22 @@ +package sops + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/schema" +) + +func New() schema.Plugin { + return schema.Plugin{ + Name: "sops", + Platform: schema.PlatformInfo{ + Name: "SOPS", + Homepage: sdk.URL("https://github.com/getsops/sops"), + }, + Credentials: []schema.CredentialType{ + AgeSecretKey(), + }, + Executables: []schema.Executable{ + SOPSCLI(), + }, + } +} diff --git a/plugins/sops/sops.go b/plugins/sops/sops.go new file mode 100644 index 00000000..0a59d5e4 --- /dev/null +++ b/plugins/sops/sops.go @@ -0,0 +1,25 @@ +package sops + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/needsauth" + "github.com/1Password/shell-plugins/sdk/schema" + "github.com/1Password/shell-plugins/sdk/schema/credname" +) + +func SOPSCLI() schema.Executable { + return schema.Executable{ + Name: "SOPS CLI", + Runs: []string{"sops"}, + DocsURL: sdk.URL("https://github.com/getsops/sops"), + NeedsAuth: needsauth.IfAll( + needsauth.NotForHelpOrVersion(), + needsauth.NotWithoutArgs(), + ), + Uses: []schema.CredentialUsage{ + { + Name: credname.SecretKey, + }, + }, + } +}