mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-24 00:15:26 +00:00
Compare commits
31 Commits
ssh-certs
...
cli-ssh-ag
Author | SHA1 | Date | |
---|---|---|---|
f5a0641671 | |||
f97f98b2c3 | |||
89848a2f5c | |||
1936f7cc4f | |||
058475fc3f | |||
ee4eb7f84b | |||
8122433f5c | |||
f3cf1a3f50 | |||
b4b417658f | |||
e70ca57510 | |||
06f321e4bf | |||
3c3fcd0db8 | |||
21eb2bed7e | |||
31a21a432d | |||
7eb05afe2a | |||
0b54948b15 | |||
39e598e408 | |||
b735618601 | |||
3a5e862def | |||
d1c4a9c75a | |||
5532844ee7 | |||
dd5aab973f | |||
ced12baf5d | |||
7db1e62654 | |||
0ab3ae442e | |||
ed9472efc8 | |||
e094844601 | |||
e761b49964 | |||
765280eef6 | |||
be36827392 | |||
097512c691 |
cli
docs
helm-charts/secrets-operator
k8-operator
PROJECT
api/v1alpha1
config
crd
rbac
samples/crd/pushsecret
controllers
infisicalpushsecret
infisicalsecret
auto_redeployment.goconditions.goinfisicalsecret_controller.goinfisicalsecret_helper.gosuite_test.go
infisicalsecret_auth.gokubectl-install
main.gopackages
12
cli/go.mod
12
cli/go.mod
@ -10,7 +10,7 @@ require (
|
||||
github.com/fatih/semgroup v1.2.0
|
||||
github.com/gitleaks/go-gitdiff v0.8.0
|
||||
github.com/h2non/filetype v1.1.3
|
||||
github.com/infisical/go-sdk v0.4.3
|
||||
github.com/infisical/go-sdk v0.4.7
|
||||
github.com/mattn/go-isatty v0.0.20
|
||||
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
|
||||
github.com/muesli/mango-cobra v1.2.0
|
||||
@ -23,8 +23,8 @@ require (
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/viper v1.8.1
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/crypto v0.25.0
|
||||
golang.org/x/term v0.22.0
|
||||
golang.org/x/crypto v0.31.0
|
||||
golang.org/x/term v0.27.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
)
|
||||
|
||||
@ -93,9 +93,9 @@ require (
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
golang.org/x/net v0.27.0 // indirect
|
||||
golang.org/x/oauth2 v0.21.0 // indirect
|
||||
golang.org/x/sync v0.7.0 // indirect
|
||||
golang.org/x/sys v0.22.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/api v0.188.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240701130421-f6361c86f094 // indirect
|
||||
|
24
cli/go.sum
24
cli/go.sum
@ -265,8 +265,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/infisical/go-sdk v0.4.3 h1:O5ZJ2eCBAZDE9PIAfBPq9Utb2CgQKrhmj9R0oFTRu4U=
|
||||
github.com/infisical/go-sdk v0.4.3/go.mod h1:6fWzAwTPIoKU49mQ2Oxu+aFnJu9n7k2JcNrZjzhHM2M=
|
||||
github.com/infisical/go-sdk v0.4.7 h1:+cxIdDfciMh0Syxbxbqjhvz9/ShnN1equ2zqlVQYGtw=
|
||||
github.com/infisical/go-sdk v0.4.7/go.mod h1:6fWzAwTPIoKU49mQ2Oxu+aFnJu9n7k2JcNrZjzhHM2M=
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo=
|
||||
github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
@ -453,8 +453,8 @@ golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -564,8 +564,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -620,16 +620,16 @@ golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -643,8 +643,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
609
cli/packages/cmd/ssh.go
Normal file
609
cli/packages/cmd/ssh.go
Normal file
@ -0,0 +1,609 @@
|
||||
/*
|
||||
Copyright (c) 2023 Infisical Inc.
|
||||
*/
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Infisical/infisical-merge/packages/api"
|
||||
"github.com/Infisical/infisical-merge/packages/config"
|
||||
"github.com/Infisical/infisical-merge/packages/util"
|
||||
infisicalSdk "github.com/infisical/go-sdk"
|
||||
infisicalSdkUtil "github.com/infisical/go-sdk/packages/util"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
|
||||
var sshCmd = &cobra.Command{
|
||||
Example: `infisical ssh`,
|
||||
Short: "Used to issue SSH credentials",
|
||||
Use: "ssh",
|
||||
DisableFlagsInUseLine: true,
|
||||
Args: cobra.NoArgs,
|
||||
}
|
||||
|
||||
var sshIssueCredentialsCmd = &cobra.Command{
|
||||
Example: `ssh issue-credentials`,
|
||||
Short: "Used to issue SSH credentials against a certificate template",
|
||||
Use: "issue-credentials",
|
||||
DisableFlagsInUseLine: true,
|
||||
Args: cobra.NoArgs,
|
||||
Run: issueCredentials,
|
||||
}
|
||||
|
||||
var sshSignKeyCmd = &cobra.Command{
|
||||
Example: `ssh sign-key`,
|
||||
Short: "Used to sign a SSH public key against a certificate template",
|
||||
Use: "sign-key",
|
||||
DisableFlagsInUseLine: true,
|
||||
Args: cobra.NoArgs,
|
||||
Run: signKey,
|
||||
}
|
||||
|
||||
var algoToFileName = map[infisicalSdkUtil.CertKeyAlgorithm]string{
|
||||
infisicalSdkUtil.RSA2048: "id_rsa_2048",
|
||||
infisicalSdkUtil.RSA4096: "id_rsa_4096",
|
||||
infisicalSdkUtil.ECDSAP256: "id_ecdsa_p256",
|
||||
infisicalSdkUtil.ECDSAP384: "id_ecdsa_p384",
|
||||
}
|
||||
|
||||
func isValidKeyAlgorithm(algo infisicalSdkUtil.CertKeyAlgorithm) bool {
|
||||
_, exists := algoToFileName[algo]
|
||||
return exists
|
||||
}
|
||||
|
||||
func isValidCertType(certType infisicalSdkUtil.SshCertType) bool {
|
||||
switch certType {
|
||||
case infisicalSdkUtil.UserCert, infisicalSdkUtil.HostCert:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func writeToFile(filePath string, content string, perm os.FileMode) error {
|
||||
// Ensure the directory exists
|
||||
dir := filepath.Dir(filePath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create directory %s: %w", dir, err)
|
||||
}
|
||||
|
||||
// Write the content to the file
|
||||
err := os.WriteFile(filePath, []byte(content), perm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to write to file %s: %w", filePath, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addCredentialsToAgent(privateKeyContent, certContent string) error {
|
||||
// Parse the private key
|
||||
privateKey, err := ssh.ParseRawPrivateKey([]byte(privateKeyContent))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse private key: %w", err)
|
||||
}
|
||||
|
||||
// Parse the certificate
|
||||
pubKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(certContent))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse certificate: %w", err)
|
||||
}
|
||||
|
||||
cert, ok := pubKey.(*ssh.Certificate)
|
||||
if !ok {
|
||||
return fmt.Errorf("parsed key is not a certificate")
|
||||
}
|
||||
// Calculate LifetimeSecs based on certificate's valid-to time
|
||||
validUntil := time.Unix(int64(cert.ValidBefore), 0)
|
||||
now := time.Now()
|
||||
|
||||
// Handle ValidBefore as either a timestamp or an enumeration
|
||||
// SSH certificates use ValidBefore as a timestamp unless set to 0 or ~0
|
||||
if cert.ValidBefore == ssh.CertTimeInfinity {
|
||||
// If certificate never expires, set default lifetime to 1 year (can adjust as needed)
|
||||
validUntil = now.Add(365 * 24 * time.Hour)
|
||||
}
|
||||
|
||||
// Calculate the duration until expiration
|
||||
lifetime := validUntil.Sub(now)
|
||||
if lifetime <= 0 {
|
||||
return fmt.Errorf("certificate is already expired")
|
||||
}
|
||||
|
||||
// Convert duration to seconds
|
||||
lifetimeSecs := uint32(lifetime.Seconds())
|
||||
|
||||
// Connect to the SSH agent
|
||||
socket := os.Getenv("SSH_AUTH_SOCK")
|
||||
if socket == "" {
|
||||
return fmt.Errorf("SSH_AUTH_SOCK not set")
|
||||
}
|
||||
|
||||
conn, err := net.Dial("unix", socket)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to SSH agent: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
agentClient := agent.NewClient(conn)
|
||||
|
||||
// Add the key with certificate to the agent
|
||||
err = agentClient.Add(agent.AddedKey{
|
||||
PrivateKey: privateKey,
|
||||
Certificate: cert,
|
||||
Comment: "Added via Infisical CLI",
|
||||
LifetimeSecs: lifetimeSecs,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to add key to agent: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func issueCredentials(cmd *cobra.Command, args []string) {
|
||||
|
||||
token, err := util.GetInfisicalToken(cmd)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
var infisicalToken string
|
||||
|
||||
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
|
||||
infisicalToken = token.Token
|
||||
} else {
|
||||
util.RequireLogin()
|
||||
util.RequireLocalWorkspaceFile()
|
||||
|
||||
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to authenticate")
|
||||
}
|
||||
|
||||
if loggedInUserDetails.LoginExpired {
|
||||
util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
||||
}
|
||||
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
|
||||
}
|
||||
|
||||
certificateTemplateId, err := cmd.Flags().GetString("certificateTemplateId")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
if certificateTemplateId == "" {
|
||||
util.PrintErrorMessageAndExit("You must set the --certificateTemplateId flag")
|
||||
}
|
||||
|
||||
principalsStr, err := cmd.Flags().GetString("principals")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
// Check if the input string is empty before splitting
|
||||
if principalsStr == "" {
|
||||
util.HandleError(fmt.Errorf("no principals provided"), "The 'principals' flag cannot be empty")
|
||||
}
|
||||
|
||||
// Convert the comma-delimited string into a slice of strings
|
||||
principals := strings.Split(principalsStr, ",")
|
||||
for i, principal := range principals {
|
||||
principals[i] = strings.TrimSpace(principal)
|
||||
}
|
||||
|
||||
keyAlgorithm, err := cmd.Flags().GetString("keyAlgorithm")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse keyAlgorithm flag")
|
||||
}
|
||||
|
||||
if !isValidKeyAlgorithm(infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)) {
|
||||
util.HandleError(fmt.Errorf("invalid keyAlgorithm: %s", keyAlgorithm),
|
||||
"Valid values: RSA_2048, RSA_4096, EC_prime256v1, EC_secp384r1")
|
||||
}
|
||||
|
||||
certType, err := cmd.Flags().GetString("certType")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
if !isValidCertType(infisicalSdkUtil.SshCertType(certType)) {
|
||||
util.HandleError(fmt.Errorf("invalid certType: %s", certType),
|
||||
"Valid values: user, host")
|
||||
}
|
||||
|
||||
ttl, err := cmd.Flags().GetString("ttl")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
keyId, err := cmd.Flags().GetString("keyId")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
outFilePath, err := cmd.Flags().GetString("outFilePath")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
addToAgent, err := cmd.Flags().GetBool("addToAgent")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse addToAgent flag")
|
||||
}
|
||||
|
||||
if outFilePath == "" && addToAgent == false {
|
||||
util.PrintErrorMessageAndExit("You must provide either --outFilePath or --addToAgent flag to use this command")
|
||||
}
|
||||
|
||||
var (
|
||||
outputDir string
|
||||
privateKeyPath string
|
||||
publicKeyPath string
|
||||
signedKeyPath string
|
||||
)
|
||||
|
||||
if outFilePath != "" {
|
||||
// Expand ~ to home directory if present
|
||||
if strings.HasPrefix(outFilePath, "~") {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
util.HandleError(err, "Failed to resolve home directory")
|
||||
}
|
||||
outFilePath = strings.Replace(outFilePath, "~", homeDir, 1)
|
||||
}
|
||||
|
||||
// Check if outFilePath ends with "-cert.pub"
|
||||
if strings.HasSuffix(outFilePath, "-cert.pub") {
|
||||
// Treat outFilePath as the signed key path
|
||||
signedKeyPath = outFilePath
|
||||
|
||||
// Derive the base name by removing "-cert.pub"
|
||||
baseName := strings.TrimSuffix(filepath.Base(outFilePath), "-cert.pub")
|
||||
|
||||
// Set the output directory
|
||||
outputDir = filepath.Dir(outFilePath)
|
||||
|
||||
// Define private and public key paths
|
||||
privateKeyPath = filepath.Join(outputDir, baseName)
|
||||
publicKeyPath = filepath.Join(outputDir, baseName+".pub")
|
||||
} else {
|
||||
// Treat outFilePath as a directory
|
||||
outputDir = outFilePath
|
||||
|
||||
// Check if the directory exists; if not, create it
|
||||
info, err := os.Stat(outputDir)
|
||||
if os.IsNotExist(err) {
|
||||
err = os.MkdirAll(outputDir, 0755)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Failed to create output directory")
|
||||
}
|
||||
} else if err != nil {
|
||||
util.HandleError(err, "Failed to access output directory")
|
||||
} else if !info.IsDir() {
|
||||
util.PrintErrorMessageAndExit("The provided --outFilePath is not a directory")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define file names based on key algorithm
|
||||
fileName := algoToFileName[infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)]
|
||||
|
||||
// Define file paths
|
||||
privateKeyPath = filepath.Join(outputDir, fileName)
|
||||
publicKeyPath = filepath.Join(outputDir, fileName+".pub")
|
||||
signedKeyPath = filepath.Join(outputDir, fileName+"-cert.pub")
|
||||
|
||||
// If outFilePath ends with "-cert.pub", ensure the signedKeyPath is set
|
||||
if strings.HasSuffix(outFilePath, "-cert.pub") {
|
||||
// Ensure the signedKeyPath was set
|
||||
if signedKeyPath == "" {
|
||||
util.HandleError(fmt.Errorf("signedKeyPath is not set correctly"), "Internal error")
|
||||
}
|
||||
} else {
|
||||
// Ensure all paths are set
|
||||
if privateKeyPath == "" || publicKeyPath == "" || signedKeyPath == "" {
|
||||
util.HandleError(fmt.Errorf("file paths are not set correctly"), "Internal error")
|
||||
}
|
||||
}
|
||||
|
||||
infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{
|
||||
SiteUrl: config.INFISICAL_URL,
|
||||
UserAgent: api.USER_AGENT,
|
||||
AutoTokenRefresh: false,
|
||||
})
|
||||
infisicalClient.Auth().SetAccessToken(infisicalToken)
|
||||
|
||||
creds, err := infisicalClient.Ssh().IssueCredentials(infisicalSdk.IssueSshCredsOptions{
|
||||
CertificateTemplateID: certificateTemplateId,
|
||||
Principals: principals,
|
||||
KeyAlgorithm: infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm),
|
||||
CertType: infisicalSdkUtil.SshCertType(certType),
|
||||
TTL: ttl,
|
||||
KeyID: keyId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
util.HandleError(err, "Failed to issue SSH credentials")
|
||||
}
|
||||
|
||||
if outFilePath != "" {
|
||||
// If signedKeyPath wasn't set in the directory scenario, set it now
|
||||
if signedKeyPath == "" {
|
||||
fileName := algoToFileName[infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)]
|
||||
signedKeyPath = filepath.Join(outputDir, fileName+"-cert.pub")
|
||||
}
|
||||
|
||||
if privateKeyPath == "" {
|
||||
privateKeyPath = filepath.Join(outputDir, algoToFileName[infisicalSdkUtil.CertKeyAlgorithm(keyAlgorithm)])
|
||||
}
|
||||
err = writeToFile(privateKeyPath, creds.PrivateKey, 0600)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Failed to write Private Key to file")
|
||||
}
|
||||
|
||||
if publicKeyPath == "" {
|
||||
publicKeyPath = privateKeyPath + ".pub"
|
||||
}
|
||||
err = writeToFile(publicKeyPath, creds.PublicKey, 0644)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Failed to write Public Key to file")
|
||||
}
|
||||
|
||||
err = writeToFile(signedKeyPath, creds.SignedKey, 0644)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Failed to write Signed Key to file")
|
||||
}
|
||||
|
||||
fmt.Println("Successfully wrote SSH certificate to:", signedKeyPath)
|
||||
}
|
||||
|
||||
// Add SSH credentials to the SSH agent if needed
|
||||
if addToAgent {
|
||||
// Call the helper function to handle add-to-agent flow
|
||||
err := addCredentialsToAgent(creds.PrivateKey, creds.SignedKey)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Failed to add keys to SSH agent")
|
||||
} else {
|
||||
fmt.Println("The SSH key and certificate have been successfully added to your ssh-agent.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func signKey(cmd *cobra.Command, args []string) {
|
||||
|
||||
token, err := util.GetInfisicalToken(cmd)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
var infisicalToken string
|
||||
|
||||
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
|
||||
infisicalToken = token.Token
|
||||
} else {
|
||||
util.RequireLogin()
|
||||
util.RequireLocalWorkspaceFile()
|
||||
|
||||
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails(true)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to authenticate")
|
||||
}
|
||||
|
||||
if loggedInUserDetails.LoginExpired {
|
||||
util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
||||
}
|
||||
infisicalToken = loggedInUserDetails.UserCredentials.JTWToken
|
||||
}
|
||||
|
||||
certificateTemplateId, err := cmd.Flags().GetString("certificateTemplateId")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
if certificateTemplateId == "" {
|
||||
util.PrintErrorMessageAndExit("You must set the --certificateTemplateId flag")
|
||||
}
|
||||
|
||||
publicKey, err := cmd.Flags().GetString("publicKey")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
publicKeyFilePath, err := cmd.Flags().GetString("publicKeyFilePath")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
if publicKey == "" && publicKeyFilePath == "" {
|
||||
util.HandleError(fmt.Errorf("either --publicKey or --publicKeyFilePath must be provided"), "Invalid input")
|
||||
}
|
||||
|
||||
if publicKey != "" && publicKeyFilePath != "" {
|
||||
util.HandleError(fmt.Errorf("only one of --publicKey or --publicKeyFile can be provided"), "Invalid input")
|
||||
}
|
||||
|
||||
if publicKeyFilePath != "" {
|
||||
if strings.HasPrefix(publicKeyFilePath, "~") {
|
||||
// Expand the tilde (~) to the user's home directory
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
util.HandleError(err, "Failed to resolve home directory")
|
||||
}
|
||||
publicKeyFilePath = strings.Replace(publicKeyFilePath, "~", homeDir, 1)
|
||||
}
|
||||
|
||||
// Ensure the file has a .pub extension
|
||||
if !strings.HasSuffix(publicKeyFilePath, ".pub") {
|
||||
util.HandleError(fmt.Errorf("public key file must have a .pub extension"), "Invalid input")
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(publicKeyFilePath)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Failed to read public key file")
|
||||
}
|
||||
|
||||
publicKey = strings.TrimSpace(string(content))
|
||||
}
|
||||
|
||||
if strings.TrimSpace(publicKey) == "" {
|
||||
util.HandleError(fmt.Errorf("Public key is empty"), "Invalid input")
|
||||
}
|
||||
|
||||
principalsStr, err := cmd.Flags().GetString("principals")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
// Check if the input string is empty before splitting
|
||||
if principalsStr == "" {
|
||||
util.HandleError(fmt.Errorf("no principals provided"), "The 'principals' flag cannot be empty")
|
||||
}
|
||||
|
||||
// Convert the comma-delimited string into a slice of strings
|
||||
principals := strings.Split(principalsStr, ",")
|
||||
for i, principal := range principals {
|
||||
principals[i] = strings.TrimSpace(principal)
|
||||
}
|
||||
|
||||
certType, err := cmd.Flags().GetString("certType")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
if !isValidCertType(infisicalSdkUtil.SshCertType(certType)) {
|
||||
util.HandleError(fmt.Errorf("invalid certType: %s", certType),
|
||||
"Valid values: user, host")
|
||||
}
|
||||
|
||||
ttl, err := cmd.Flags().GetString("ttl")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
keyId, err := cmd.Flags().GetString("keyId")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
outFilePath, err := cmd.Flags().GetString("outFilePath")
|
||||
if err != nil {
|
||||
util.HandleError(err, "Unable to parse flag")
|
||||
}
|
||||
|
||||
var (
|
||||
outputDir string
|
||||
signedKeyPath string
|
||||
)
|
||||
|
||||
if outFilePath == "" {
|
||||
// Use current working directory
|
||||
if err != nil {
|
||||
util.HandleError(err, "Failed to get current working directory")
|
||||
}
|
||||
|
||||
// check if public key path exists
|
||||
if publicKeyFilePath == "" {
|
||||
util.PrintErrorMessageAndExit("--outFilePath must be specified when --publicKeyFilePath is not provided")
|
||||
}
|
||||
|
||||
outputDir = filepath.Dir(publicKeyFilePath)
|
||||
// Derive the base name by removing "-cert.pub"
|
||||
baseName := strings.TrimSuffix(filepath.Base(publicKeyFilePath), ".pub")
|
||||
signedKeyPath = filepath.Join(outputDir, baseName+"-cert.pub")
|
||||
} else {
|
||||
// Expand ~ to home directory if present
|
||||
if strings.HasPrefix(outFilePath, "~") {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
util.HandleError(err, "Failed to resolve home directory")
|
||||
}
|
||||
outFilePath = strings.Replace(outFilePath, "~", homeDir, 1)
|
||||
}
|
||||
|
||||
// Check if outFilePath ends with "-cert.pub"
|
||||
if !strings.HasSuffix(outFilePath, "-cert.pub") {
|
||||
util.PrintErrorMessageAndExit("--outFilePath must end with -cert.pub")
|
||||
}
|
||||
|
||||
// Extract the directory from outFilePath
|
||||
outputDir = filepath.Dir(outFilePath)
|
||||
|
||||
// Validate the output directory
|
||||
info, err := os.Stat(outputDir)
|
||||
if os.IsNotExist(err) {
|
||||
// Directory does not exist; attempt to create it
|
||||
err = os.MkdirAll(outputDir, 0755)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Failed to create output directory")
|
||||
}
|
||||
} else if err != nil {
|
||||
// Other errors accessing the directory
|
||||
util.HandleError(err, "Failed to access output directory")
|
||||
} else if !info.IsDir() {
|
||||
// Path exists but is not a directory
|
||||
util.PrintErrorMessageAndExit("The provided --outFilePath's directory is not valid")
|
||||
}
|
||||
|
||||
signedKeyPath = outFilePath
|
||||
}
|
||||
|
||||
infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{
|
||||
SiteUrl: config.INFISICAL_URL,
|
||||
UserAgent: api.USER_AGENT,
|
||||
AutoTokenRefresh: false,
|
||||
})
|
||||
infisicalClient.Auth().SetAccessToken(infisicalToken)
|
||||
|
||||
creds, err := infisicalClient.Ssh().SignKey(infisicalSdk.SignSshPublicKeyOptions{
|
||||
CertificateTemplateID: certificateTemplateId,
|
||||
PublicKey: publicKey,
|
||||
Principals: principals,
|
||||
CertType: infisicalSdkUtil.SshCertType(certType),
|
||||
TTL: ttl,
|
||||
KeyID: keyId,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
util.HandleError(err, "Failed to sign SSH public key")
|
||||
}
|
||||
|
||||
err = writeToFile(signedKeyPath, creds.SignedKey, 0644)
|
||||
if err != nil {
|
||||
util.HandleError(err, "Failed to write Signed Key to file")
|
||||
}
|
||||
|
||||
fmt.Println("Successfully wrote SSH certificate to:", signedKeyPath)
|
||||
}
|
||||
|
||||
func init() {
|
||||
sshSignKeyCmd.Flags().String("token", "", "Issue SSH certificate using machine identity access token")
|
||||
sshSignKeyCmd.Flags().String("certificateTemplateId", "", "The ID of the SSH certificate template to issue the SSH certificate for")
|
||||
sshSignKeyCmd.Flags().String("publicKey", "", "The public key to sign")
|
||||
sshSignKeyCmd.Flags().String("publicKeyFilePath", "", "The file path to the public key file to sign")
|
||||
sshSignKeyCmd.Flags().String("outFilePath", "", "The path to write the SSH certificate to such as ~/.ssh/id_rsa-cert.pub. If not provided, the credentials will be saved to the directory of the specified public key file path or the current working directory")
|
||||
sshSignKeyCmd.Flags().String("principals", "", "The principals that the certificate should be signed for")
|
||||
sshSignKeyCmd.Flags().String("certType", string(infisicalSdkUtil.UserCert), "The cert type for the created certificate")
|
||||
sshSignKeyCmd.Flags().String("ttl", "", "The ttl for the created certificate")
|
||||
sshSignKeyCmd.Flags().String("keyId", "", "The keyId that the created certificate should have")
|
||||
sshCmd.AddCommand(sshSignKeyCmd)
|
||||
|
||||
sshIssueCredentialsCmd.Flags().String("token", "", "Issue SSH credentials using machine identity access token")
|
||||
sshIssueCredentialsCmd.Flags().String("certificateTemplateId", "", "The ID of the SSH certificate template to issue SSH credentials for")
|
||||
sshIssueCredentialsCmd.Flags().String("principals", "", "The principals to issue SSH credentials for")
|
||||
sshIssueCredentialsCmd.Flags().String("keyAlgorithm", string(infisicalSdkUtil.RSA2048), "The key algorithm to issue SSH credentials for")
|
||||
sshIssueCredentialsCmd.Flags().String("certType", string(infisicalSdkUtil.UserCert), "The cert type to issue SSH credentials for")
|
||||
sshIssueCredentialsCmd.Flags().String("ttl", "", "The ttl to issue SSH credentials for")
|
||||
sshIssueCredentialsCmd.Flags().String("keyId", "", "The keyId to issue SSH credentials for")
|
||||
sshIssueCredentialsCmd.Flags().String("outFilePath", "", "The path to write the SSH credentials to such as ~/.ssh, ./some_folder, ./some_folder/id_rsa-cert.pub. If not provided, the credentials will be saved to the current working directory")
|
||||
sshIssueCredentialsCmd.Flags().Bool("addToAgent", false, "Whether to add issued SSH credentials to the SSH agent")
|
||||
sshCmd.AddCommand(sshIssueCredentialsCmd)
|
||||
rootCmd.AddCommand(sshCmd)
|
||||
}
|
@ -159,7 +159,19 @@ as part of the SSH operation.
|
||||
|
||||
## Guide to Using Infisical SSH to Access a Host
|
||||
|
||||
We show how to obtain a SSH certificate (and optionally a new SSH key pair) for a client to access a host via CLI:
|
||||
We show how to obtain a SSH certificate and use it for a client to access a host via CLI:
|
||||
|
||||
<Note>
|
||||
The subsequent guide assumes the following prerequisites:
|
||||
|
||||
- SSH Agent is running: The `ssh-agent` must be actively running on the host machine.
|
||||
- OpenSSH is installed: The system should have OpenSSH installed; this includes
|
||||
both the `ssh` client and `ssh-agent`.
|
||||
- `SSH_AUTH_SOCK` environment variable
|
||||
is set; the `SSH_AUTH_SOCK` variable should point to the UNIX socket that
|
||||
`ssh-agent` uses for communication.
|
||||
|
||||
</Note>
|
||||
|
||||
<Steps>
|
||||
<Step title="Authenticate with Infisical">
|
||||
@ -169,70 +181,25 @@ infisical login
|
||||
```
|
||||
|
||||
</Step>
|
||||
<Step title="Obtain a SSH certificate (and optionally new key-pair)">
|
||||
Depending on the use-case, a client may either request a SSH certificate along with a new SSH key pair or obtain a SSH certificate for an existing SSH key pair to access a host.
|
||||
<Step title="Obtain a SSH certificate and load it into the SSH agent">
|
||||
Run the `infisical ssh issue-credentials` command, specifying the `--addToAgent` flag to automatically load the SSH certificate into the SSH agent.
|
||||
```bash
|
||||
infisical ssh issue-credentials --certificateTemplateId=<certificate-template-id> --principals=<username> --addToAgent
|
||||
```
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Using New Key Pair (Recommended)">
|
||||
If you wish to obtain a new SSH key pair in conjunction with the SSH certificate, then you can use the `infisical ssh issue-credentials` command.
|
||||
Here's some guidance on each flag:
|
||||
|
||||
```bash
|
||||
infisical ssh issue-credentials --certificateTemplateId=<certificate-template-id> --principals=<username>
|
||||
```
|
||||
|
||||
The following flags may be relevant:
|
||||
|
||||
- `certificateTemplateId`: The ID of the certificate template to use for issuing the SSH certificate.
|
||||
- `principals`: The comma-delimited username(s) or hostname(s) to include in the SSH certificate.
|
||||
- `outFilePath` (optional): The path to the file to write the SSH certificate to.
|
||||
|
||||
<Note>
|
||||
If `outFilePath` is not specified, the SSH certificate will be written to the current working directory where the command is run.
|
||||
</Note>
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Using Existing Key Pair">
|
||||
If you have an existing SSH key pair, then you can use the `infisical ssh sign-key` command with either
|
||||
the `--publicKey` flag or the `--publicKeyFilePath` flag to obtain a SSH certificate corresponding to
|
||||
the existing credential.
|
||||
|
||||
```bash
|
||||
infisical ssh sign-key --publicKeyFilePath=<public-key-file-path> --certificateTemplateId=<certificate-template-id> --principals=<username>
|
||||
```
|
||||
|
||||
The following flags may be relevant:
|
||||
|
||||
- `publicKey`: The public key to sign.
|
||||
- `publicKeyFilePath`: The path to the public key file to sign.
|
||||
- `certificateTemplateId`: The ID of the certificate template to use for issuing the SSH certificate.
|
||||
- `principals`: The comma-delimited username(s) or hostname(s) to include in the SSH certificate.
|
||||
- `outFilePath` (optional): The path to the file to write the SSH certificate to.
|
||||
|
||||
<Note>
|
||||
If `outFilePath` is not specified but `publicKeyFilePath` is then the SSH certificate will be written to the directory of the public key file; if the public key file is called `id_rsa.pub`, then the file containing the SSH certificate will be called `id_rsa-cert.pub`.
|
||||
|
||||
Otherwise, if `outFilePath` is not specified, the SSH certificate will be written to the current working directory where the command is run.
|
||||
</Note>
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
- `certificateTemplateId`: The ID of the certificate template to use for issuing the SSH certificate.
|
||||
- `principals`: The comma-delimited username(s) or hostname(s) to include in the SSH certificate.
|
||||
|
||||
</Step>
|
||||
<Step title="SSH into the host">
|
||||
Once you have obtained the SSH certificate, you can use it to SSH into the desired host.
|
||||
Finally, SSH into the desired host; the SSH operation will be performed using the SSH certificate loaded into the SSH agent.
|
||||
|
||||
```bash
|
||||
ssh -i /path/to/private_key.pem \
|
||||
-o CertificateFile=/path/to/ssh-cert.pub \
|
||||
username@hostname
|
||||
ssh username@hostname
|
||||
```
|
||||
|
||||
<Note>
|
||||
We recommend setting up aliases so you can more easily SSH into the desired host.
|
||||
|
||||
For example, you may set up an SSH alias using the SSH client configuration file (usually `~/.ssh/config`), defining a host alias including the file path to the issued SSH credential(s).
|
||||
</Note>
|
||||
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
|
@ -831,9 +831,9 @@ type: Opaque
|
||||
|
||||
</Accordion>
|
||||
|
||||
### Apply the Infisical CRD to your cluster
|
||||
### Apply the InfisicalSecret CRD to your cluster
|
||||
|
||||
Once you have configured the Infisical CRD with the required fields, you can apply it to your cluster.
|
||||
Once you have configured the InfisicalSecret CRD with the required fields, you can apply it to your cluster.
|
||||
After applying, you should notice that the managed secret has been created in the desired namespace your specified.
|
||||
|
||||
```
|
||||
@ -1032,12 +1032,12 @@ stringData:
|
||||
-----END CERTIFICATE-----
|
||||
```
|
||||
|
||||
## Auto redeployment
|
||||
### Auto redeployment
|
||||
|
||||
Deployments using managed secrets don't reload automatically on updates, so they may use outdated secrets unless manually redeployed.
|
||||
To address this, we added functionality to automatically redeploy your deployment when its managed secret updates.
|
||||
|
||||
### Enabling auto redeploy
|
||||
#### Enabling auto redeploy
|
||||
|
||||
To enable auto redeployment you simply have to add the following annotation to the deployment that consumes a managed secret
|
||||
|
||||
@ -1080,6 +1080,419 @@ spec:
|
||||
When a secret change occurs, the operator will check to see which deployments are using the operator-managed Kubernetes secret that received the update.
|
||||
Then, for each deployment that has this annotation present, a rolling update will be triggered.
|
||||
</Info>
|
||||
|
||||
## Push Secrets to Infisical
|
||||
|
||||
|
||||
### Example usage
|
||||
|
||||
Below is a sample InfisicalPushSecret CRD that pushes secrets defined in a Kubernetes secret to Infisical.
|
||||
|
||||
After filling out the fields in the InfisicalPushSecret CRD, you can apply it directly to your cluster.
|
||||
|
||||
Before applying the InfisicalPushSecret CRD, you need to create a Kubernetes secret containing the secrets you want to push to Infisical. An example can be seen below the InfisicalPushSecret CRD.
|
||||
|
||||
```bash
|
||||
kubectl apply -f source-secret.yaml
|
||||
```
|
||||
|
||||
After applying the soruce-secret.yaml file, you are ready to apply the InfisicalPushSecret CRD.
|
||||
|
||||
```bash
|
||||
kubectl apply -f infisical-push-secret.yaml
|
||||
```
|
||||
|
||||
After applying the InfisicalPushSecret CRD, you should notice that the secrets you have defined in your source-secret.yaml file have been pushed to your specified destination in Infisical.
|
||||
|
||||
```yaml infisical-push-secret.yaml
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalPushSecret
|
||||
metadata:
|
||||
name: infisical-push-secret-demo
|
||||
spec:
|
||||
resyncInterval: 1m
|
||||
hostAPI: https://app.infisical.com/api
|
||||
|
||||
# Optional, defaults to no replacement.
|
||||
updatePolicy: Replace # If set to replace, existing secrets inside Infisical will be replaced by the value of the PushSecret on sync.
|
||||
|
||||
# Optional, defaults to no deletion.
|
||||
deletionPolicy: Delete # If set to delete, the secret(s) inside Infisical managed by the operator, will be deleted if the InfisicalPushSecret CRD is deleted.
|
||||
|
||||
destination:
|
||||
projectId: <project-id>
|
||||
environmentSlug: <env-slug>
|
||||
secretsPath: <secret-path>
|
||||
|
||||
push:
|
||||
secret:
|
||||
secretName: push-secret-demo # Secret CRD
|
||||
secretNamespace: default
|
||||
|
||||
# Only have one authentication method defined or you are likely to run into authentication issues.
|
||||
# Remove all except one authentication method.
|
||||
authentication:
|
||||
awsIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
azureAuth:
|
||||
identityId: <machine-identity-id>
|
||||
gcpIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountKeyFilePath: </path-to-service-account-key-file.json>
|
||||
gcpIdTokenAuth:
|
||||
identityId: <machine-identity-id>
|
||||
kubernetesAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountRef:
|
||||
name: <secret-name>
|
||||
namespace: <secret-namespace>
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
secretName: <secret-name> # universal-auth-credentials
|
||||
secretNamespace: <secret-namespace> # default
|
||||
```
|
||||
|
||||
```yaml source-secret.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: push-secret-demo
|
||||
namespace: default
|
||||
stringData: # can also be "data", but needs to be base64 encoded
|
||||
API_KEY: some-api-key
|
||||
DATABASE_URL: postgres://127.0.0.1:5432
|
||||
ENCRYPTION_KEY: fabcc12-a22-facbaa4-11aa568aab
|
||||
```
|
||||
|
||||
### InfisicalPushSecret CRD properties
|
||||
|
||||
<Accordion title="hostAPI">
|
||||
If you are fetching secrets from a self-hosted instance of Infisical set the value of `hostAPI` to
|
||||
` https://your-self-hosted-instace.com/api`
|
||||
|
||||
When `hostAPI` is not defined the operator fetches secrets from Infisical Cloud.
|
||||
|
||||
<Accordion title="Advanced use case">
|
||||
If you have installed your Infisical instance within the same cluster as the Infisical operator, you can optionally access the Infisical backend's service directly without having to route through the public internet.
|
||||
To achieve this, use the following address for the hostAPI field:
|
||||
|
||||
``` bash
|
||||
http://<backend-svc-name>.<namespace>.svc.cluster.local:4000/api
|
||||
```
|
||||
|
||||
Make sure to replace `<backend-svc-name>` and `<namespace>` with the appropriate values for your backend service and namespace.
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="resyncInterval">
|
||||
|
||||
The `resyncInterval` is a string-formatted duration that defines the time between each resync.
|
||||
|
||||
The format of the field is `[duration][unit]` where `duration` is a number and `unit` is a string representing the unit of time.
|
||||
|
||||
The following units are supported:
|
||||
- `s` for seconds (must be at least 5 seconds)
|
||||
- `m` for minutes
|
||||
- `h` for hours
|
||||
- `d` for days
|
||||
- `w` for weeks
|
||||
|
||||
The default value is `1m` (1 minute).
|
||||
|
||||
Valid intervals examples:
|
||||
```yaml
|
||||
resyncInterval: 5s # 10 seconds
|
||||
resyncInterval: 10s # 10 seconds
|
||||
resyncInterval: 5m # 5 minutes
|
||||
resyncInterval: 1h # 1 hour
|
||||
resyncInterval: 1d # 1 day
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="updatePolicy">
|
||||
|
||||
The field is optional and will default to `None` if not defined.
|
||||
|
||||
The update policy defines how the operator should handle conflicting secrets when pushing secrets to Infisical.
|
||||
|
||||
Valid values are `None` and `Replace`.
|
||||
|
||||
Behavior of each policy:
|
||||
- `None`: The operator will not override existing secrets in Infisical. If a secret with the same key already exists, the operator will skip pushing that secret, and the secret will not be managed by the operator.
|
||||
- `Replace`: The operator will replace existing secrets in Infisical with the new secrets. If a secret with the same key already exists, the operator will update the secret with the new value.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
updatePolicy: Replace
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="deletionPolicy">
|
||||
|
||||
This field is optional and will default to `None` if not defined.
|
||||
|
||||
The deletion policy defines what the operator should do in case the InfisicalPushSecret CRD is deleted.
|
||||
|
||||
Valid values are `None` and `Delete`.
|
||||
|
||||
Behavior of each policy:
|
||||
- `None`: The operator will not delete the secrets in Infisical when the InfisicalPushSecret CRD is deleted.
|
||||
- `Delete`: The operator will delete the secrets in Infisical that are managed by the operator when the InfisicalPushSecret CRD is deleted.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
deletionPolicy: Delete
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="destination">
|
||||
The `destination` field is used to specify where you want to create the secrets in Infisical. The required fields are `projectId`, `environmentSlug`, and `secretsPath`.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
destination:
|
||||
projectId: <project-id>
|
||||
environmentSlug: <env-slug>
|
||||
secretsPath: <secrets-path>
|
||||
```
|
||||
|
||||
<Accordion title="destination.projectId">
|
||||
The project ID where you want to create the secrets in Infisical.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="destination.environmentSlug">
|
||||
The environment slug where you want to create the secrets in Infisical.
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="destination.secretsPath">
|
||||
The path where you want to create the secrets in Infisical. The root path is `/`.
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="push">
|
||||
The `push` field is used to define what you want to push to Infisical. Currently the operator only supports pushing Kubernetes secrets to Infisical. An example of the `push` field is shown below.
|
||||
|
||||
|
||||
|
||||
<Accordion title="secret">
|
||||
The `secret` field is used to define the Kubernetes secret you want to push to Infisical. The required fields are `secretName` and `secretNamespace`.
|
||||
|
||||
|
||||
|
||||
Example usage of the `push.secret` field:
|
||||
|
||||
```yaml infisical-push-secret.yaml
|
||||
push:
|
||||
secret:
|
||||
secretName: push-secret-demo
|
||||
secretNamespace: default
|
||||
```
|
||||
|
||||
```yaml push-secret-demo.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: push-secret-demo
|
||||
namespace: default
|
||||
# Pass in the secrets you wish to push to Infisical
|
||||
stringData:
|
||||
API_KEY: some-api-key
|
||||
DATABASE_URL: postgres://127.0.0.1:5432
|
||||
ENCRYPTION_KEY: fabcc12-a22-facbaa4-11aa568aab
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="authentication">
|
||||
|
||||
The `authentication` field dictates which authentication method to use when pushing secrets to Infisical.
|
||||
The available authentication methods are `universalAuth`, `kubernetesAuth`, `awsIamAuth`, `azureAuth`, `gcpIdTokenAuth`, and `gcpIamAuth`.
|
||||
|
||||
|
||||
<Accordion title="universalAuth">
|
||||
The universal authentication method is one of the easiest ways to get started with Infisical. Universal Auth works anywhere and is not tied to any specific cloud provider.
|
||||
[Read more about Universal Auth](/documentation/platform/identities/universal-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
- `credentialsRef`: The name and namespace of the Kubernetes secret that stores the service token.
|
||||
- `credentialsRef.secretName`: The name of the Kubernetes secret.
|
||||
- `credentialsRef.secretNamespace`: The namespace of the Kubernetes secret.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
# infisical-push-secret.yaml
|
||||
spec:
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
secretName: <secret-name>
|
||||
secretNamespace: <secret-namespace>
|
||||
```
|
||||
|
||||
```yaml
|
||||
# machine-identity-credentials.yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: universal-auth-credentials
|
||||
type: Opaque
|
||||
stringData:
|
||||
clientId: <machine-identity-client-id>
|
||||
clientSecret: <machine-identity-client-secret>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="kubernetesAuth">
|
||||
The Kubernetes machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used within a Kubernetes environment.
|
||||
[Read more about Kubernetes Auth](/documentation/platform/identities/kubernetes-auth).
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
- `serviceAccountRef`: The name and namespace of the service account that will be used to authenticate with Infisical.
|
||||
- `serviceAccountRef.name`: The name of the service account.
|
||||
- `serviceAccountRef.namespace`: The namespace of the service account.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
kubernetesAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountRef:
|
||||
name: <secret-name>
|
||||
namespace: <secret-namespace>
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="awsIamAuth">
|
||||
The AWS IAM machine identity authentication method is used to authenticate with Infisical.
|
||||
[Read more about AWS IAM Auth](/documentation/platform/identities/aws-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
authentication:
|
||||
awsIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
```
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="azureAuth">
|
||||
The AWS IAM machine identity authentication method is used to authenticate with Infisical. Azure Auth can only be used from within an Azure environment.
|
||||
[Read more about Azure Auth](/documentation/platform/identities/azure-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
authentication:
|
||||
azureAuth:
|
||||
identityId: <machine-identity-id>
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="gcpIamAuth">
|
||||
The GCP IAM machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used both within and outside GCP environments.
|
||||
[Read more about Azure Auth](/documentation/platform/identities/gcp-auth).
|
||||
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
- `serviceAccountKeyFilePath`: The path to the GCP service account key file.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
gcpIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountKeyFilePath: </path-to-service-account-key-file.json>
|
||||
```
|
||||
</Accordion>
|
||||
<Accordion title="gcpIdTokenAuth">
|
||||
The GCP ID Token machine identity authentication method is used to authenticate with Infisical. The identity ID is stored in a field in the InfisicalSecret resource. This authentication method can only be used within GCP environments.
|
||||
[Read more about Azure Auth](/documentation/platform/identities/gcp-auth).
|
||||
|
||||
Valid fields:
|
||||
- `identityId`: The identity ID of the machine identity you created.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
gcpIdTokenAuth:
|
||||
identityId: <machine-identity-id>
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
|
||||
|
||||
<Accordion title="tls">
|
||||
This block defines the TLS settings to use for connecting to the Infisical
|
||||
instance.
|
||||
|
||||
Fields:
|
||||
<Accordion title="caRef">
|
||||
This block defines the reference to the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
|
||||
Valid fields:
|
||||
- `secretName`: The name of the Kubernetes secret containing the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
- `secretNamespace`: The namespace of the Kubernetes secret containing the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
- `key`: The name of the key in the Kubernetes secret which contains the value of the CA certificate to use for connecting to the Infisical instance with SSL/TLS.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml
|
||||
tls:
|
||||
caRef:
|
||||
secretName: custom-ca-certificate
|
||||
secretNamespace: default
|
||||
key: ca.crt
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
</Accordion>
|
||||
|
||||
|
||||
### Applying the InfisicalPushSecret CRD to your cluster
|
||||
|
||||
Once you have configured the `InfisicalPushSecret` CRD with the required fields, you can apply it to your cluster.
|
||||
After applying, you should notice that the secrets have been pushed to Infisical.
|
||||
|
||||
```bash
|
||||
kubectl apply -f source-push-secret.yaml # The secret that you're referencing in the InfisicalPushSecret CRD push.secret field
|
||||
kubectl apply -f example-infisical-push-secret-crd.yaml # The InfisicalPushSecret CRD itself
|
||||
```
|
||||
|
||||
### Connecting to instances with private/self-signed certificate
|
||||
|
||||
To connect to Infisical instances behind a private/self-signed certificate, you can configure the TLS settings in the `InfisicalPushSecret` CRD
|
||||
to point to a CA certificate stored in a Kubernetes secret resource.
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
hostAPI: https://app.infisical.com/api
|
||||
resyncInterval: 30s
|
||||
tls:
|
||||
caRef:
|
||||
secretName: custom-ca-certificate
|
||||
secretNamespace: default
|
||||
key: ca.crt
|
||||
authentication:
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
## Global configuration
|
||||
|
||||
To configure global settings that will apply to all instances of `InfisicalSecret`, you can define these configurations in a Kubernetes ConfigMap.
|
||||
|
@ -13,9 +13,9 @@ type: application
|
||||
# This is the chart version. This version number should be incremented each time you make changes
|
||||
# to the chart and its templates, including the app version.
|
||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||
version: v0.7.7
|
||||
version: v0.8.0
|
||||
# This is the version number of the application being deployed. This version number should be
|
||||
# incremented each time you make changes to the application. Versions are not expected to
|
||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||
# It is recommended to use it with quotes.
|
||||
appVersion: "v0.7.7"
|
||||
appVersion: "v0.8.0"
|
||||
|
@ -0,0 +1,268 @@
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
name: infisicalpushsecrets.secrets.infisical.com
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
labels:
|
||||
{{- include "secrets-operator.labels" . | nindent 4 }}
|
||||
spec:
|
||||
group: secrets.infisical.com
|
||||
names:
|
||||
kind: InfisicalPushSecret
|
||||
listKind: InfisicalPushSecretList
|
||||
plural: infisicalpushsecrets
|
||||
singular: infisicalpushsecret
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: InfisicalPushSecret is the Schema for the infisicalpushsecrets
|
||||
API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: InfisicalPushSecretSpec defines the desired state of InfisicalPushSecret
|
||||
properties:
|
||||
authentication:
|
||||
properties:
|
||||
awsIamAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
type: object
|
||||
azureAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
resource:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
type: object
|
||||
gcpIamAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
serviceAccountKeyFilePath:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
- serviceAccountKeyFilePath
|
||||
type: object
|
||||
gcpIdTokenAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
type: object
|
||||
kubernetesAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
serviceAccountRef:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
required:
|
||||
- identityId
|
||||
- serviceAccountRef
|
||||
type: object
|
||||
universalAuth:
|
||||
description: PushSecretUniversalAuth defines universal authentication
|
||||
properties:
|
||||
credentialsRef:
|
||||
properties:
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The name space where the Kubernetes Secret
|
||||
is located
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
required:
|
||||
- credentialsRef
|
||||
type: object
|
||||
type: object
|
||||
deletionPolicy:
|
||||
type: string
|
||||
destination:
|
||||
properties:
|
||||
environmentSlug:
|
||||
type: string
|
||||
projectId:
|
||||
type: string
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- environmentSlug
|
||||
- projectId
|
||||
- secretsPath
|
||||
type: object
|
||||
hostAPI:
|
||||
description: Infisical host to pull secrets from
|
||||
type: string
|
||||
push:
|
||||
properties:
|
||||
secret:
|
||||
properties:
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The name space where the Kubernetes Secret is located
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
required:
|
||||
- secret
|
||||
type: object
|
||||
resyncInterval:
|
||||
type: string
|
||||
tls:
|
||||
properties:
|
||||
caRef:
|
||||
description: Reference to secret containing CA cert
|
||||
properties:
|
||||
key:
|
||||
description: The name of the secret property with the CA certificate
|
||||
value
|
||||
type: string
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The namespace where the Kubernetes Secret is located
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
type: object
|
||||
updatePolicy:
|
||||
type: string
|
||||
required:
|
||||
- destination
|
||||
- push
|
||||
- resyncInterval
|
||||
type: object
|
||||
status:
|
||||
description: InfisicalPushSecretStatus defines the observed state of InfisicalPushSecret
|
||||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current
|
||||
state of this API Resource. --- This struct is intended for direct
|
||||
use as an array at the field path .status.conditions. For example,
|
||||
\n type FooStatus struct{ // Represents the observations of a foo's
|
||||
current state. // Known .status.conditions.type are: \"Available\",
|
||||
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
|
||||
// +listType=map // +listMapKey=type Conditions []metav1.Condition
|
||||
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
|
||||
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition
|
||||
transitioned from one status to another. This should be when
|
||||
the underlying condition changed. If that is not known, then
|
||||
using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating details
|
||||
about the transition. This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation
|
||||
that the condition was set based upon. For instance, if .metadata.generation
|
||||
is currently 12, but the .status.conditions[x].observedGeneration
|
||||
is 9, the condition is out of date with respect to the current
|
||||
state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating
|
||||
the reason for the condition's last transition. Producers of
|
||||
specific condition types may define expected values and meanings
|
||||
for this field, and whether the values are considered a guaranteed
|
||||
API. The value should be a CamelCase string. This field may
|
||||
not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
--- Many .condition.type values are consistent across resources
|
||||
like Available, but because arbitrary conditions can be useful
|
||||
(see .node.status.conditions), the ability to deconflict is
|
||||
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
managedSecrets:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: managed secrets is a map where the key is the ID, and the
|
||||
value is the secret key (string[id], string[key] )
|
||||
type: object
|
||||
required:
|
||||
- conditions
|
||||
- managedSecrets
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
status:
|
||||
acceptedNames:
|
||||
kind: ""
|
||||
plural: ""
|
||||
conditions: []
|
||||
storedVersions: []
|
@ -51,6 +51,32 @@ rules:
|
||||
- list
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicalpushsecrets
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicalpushsecrets/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicalpushsecrets/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
|
@ -32,7 +32,7 @@ controllerManager:
|
||||
- ALL
|
||||
image:
|
||||
repository: infisical/kubernetes-operator
|
||||
tag: v0.7.7
|
||||
tag: v0.8.0
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
|
@ -1,16 +1,29 @@
|
||||
# Code generated by tool. DO NOT EDIT.
|
||||
# This file is used to track the info used to scaffold your project
|
||||
# and allow the plugins properly work.
|
||||
# More info: https://book.kubebuilder.io/reference/project-config.html
|
||||
domain: infisical.com
|
||||
layout:
|
||||
- go.kubebuilder.io/v3
|
||||
- go.kubebuilder.io/v3
|
||||
projectName: k8-operator
|
||||
repo: github.com/Infisical/infisical/k8-operator
|
||||
resources:
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
controller: true
|
||||
domain: infisical.com
|
||||
group: secrets
|
||||
kind: InfisicalSecret
|
||||
path: github.com/Infisical/infisical/k8-operator/api/v1alpha1
|
||||
version: v1alpha1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
controller: true
|
||||
domain: infisical.com
|
||||
group: secrets
|
||||
kind: InfisicalSecret
|
||||
path: github.com/Infisical/infisical/k8-operator/api/v1alpha1
|
||||
version: v1alpha1
|
||||
- api:
|
||||
crdVersion: v1
|
||||
namespaced: true
|
||||
controller: true
|
||||
domain: infisical.com
|
||||
group: secrets
|
||||
kind: InfisicalPushSecretSecret
|
||||
path: github.com/Infisical/infisical/k8-operator/api/v1alpha1
|
||||
version: v1alpha1
|
||||
version: "3"
|
||||
|
140
k8-operator/api/v1alpha1/infisicalpushsecret_types.go
Normal file
140
k8-operator/api/v1alpha1/infisicalpushsecret_types.go
Normal file
@ -0,0 +1,140 @@
|
||||
package v1alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type InfisicalPushSecretDestination struct {
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Immutable
|
||||
SecretsPath string `json:"secretsPath"`
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Immutable
|
||||
EnvironmentSlug string `json:"environmentSlug"`
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Immutable
|
||||
ProjectID string `json:"projectId"`
|
||||
}
|
||||
|
||||
type PushSecretTlsConfig struct {
|
||||
// Reference to secret containing CA cert
|
||||
// +kubebuilder:validation:Optional
|
||||
CaRef CaReference `json:"caRef,omitempty"`
|
||||
}
|
||||
|
||||
// PushSecretUniversalAuth defines universal authentication
|
||||
type PushSecretUniversalAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
CredentialsRef KubeSecretReference `json:"credentialsRef"`
|
||||
}
|
||||
|
||||
type PushSecretAwsIamAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
}
|
||||
|
||||
type PushSecretAzureAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
// +kubebuilder:validation:Optional
|
||||
Resource string `json:"resource,omitempty"`
|
||||
}
|
||||
|
||||
type PushSecretGcpIdTokenAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
}
|
||||
|
||||
type PushSecretGcpIamAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
// +kubebuilder:validation:Required
|
||||
ServiceAccountKeyFilePath string `json:"serviceAccountKeyFilePath"`
|
||||
}
|
||||
|
||||
type PushSecretKubernetesAuth struct {
|
||||
// +kubebuilder:validation:Required
|
||||
IdentityID string `json:"identityId"`
|
||||
// +kubebuilder:validation:Required
|
||||
ServiceAccountRef KubernetesServiceAccountRef `json:"serviceAccountRef"`
|
||||
}
|
||||
|
||||
type PushSecretAuthentication struct {
|
||||
// +kubebuilder:validation:Optional
|
||||
UniversalAuth PushSecretUniversalAuth `json:"universalAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
KubernetesAuth PushSecretKubernetesAuth `json:"kubernetesAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
AwsIamAuth PushSecretAwsIamAuth `json:"awsIamAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
AzureAuth PushSecretAzureAuth `json:"azureAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
GcpIdTokenAuth PushSecretGcpIdTokenAuth `json:"gcpIdTokenAuth,omitempty"`
|
||||
// +kubebuilder:validation:Optional
|
||||
GcpIamAuth PushSecretGcpIamAuth `json:"gcpIamAuth,omitempty"`
|
||||
}
|
||||
|
||||
type SecretPush struct {
|
||||
// +kubebuilder:validation:Required
|
||||
Secret KubeSecretReference `json:"secret"`
|
||||
}
|
||||
|
||||
// InfisicalPushSecretSpec defines the desired state of InfisicalPushSecret
|
||||
type InfisicalPushSecretSpec struct {
|
||||
// +kubebuilder:validation:Optional
|
||||
UpdatePolicy string `json:"updatePolicy"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
DeletionPolicy string `json:"deletionPolicy"`
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
// +kubebuilder:validation:Immutable
|
||||
Destination InfisicalPushSecretDestination `json:"destination"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
Authentication PushSecretAuthentication `json:"authentication"`
|
||||
|
||||
// +kubebuilder:validation:Required
|
||||
Push SecretPush `json:"push"`
|
||||
|
||||
ResyncInterval string `json:"resyncInterval"`
|
||||
|
||||
// Infisical host to pull secrets from
|
||||
// +kubebuilder:validation:Optional
|
||||
HostAPI string `json:"hostAPI"`
|
||||
|
||||
// +kubebuilder:validation:Optional
|
||||
TLS PushSecretTlsConfig `json:"tls"`
|
||||
}
|
||||
|
||||
// InfisicalPushSecretStatus defines the observed state of InfisicalPushSecret
|
||||
type InfisicalPushSecretStatus struct {
|
||||
Conditions []metav1.Condition `json:"conditions"`
|
||||
|
||||
// managed secrets is a map where the key is the ID, and the value is the secret key (string[id], string[key] )
|
||||
ManagedSecrets map[string]string `json:"managedSecrets"`
|
||||
}
|
||||
|
||||
// +kubebuilder:object:root=true
|
||||
// +kubebuilder:subresource:status
|
||||
// InfisicalPushSecret is the Schema for the infisicalpushsecrets API
|
||||
type InfisicalPushSecret struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec InfisicalPushSecretSpec `json:"spec,omitempty"`
|
||||
Status InfisicalPushSecretStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
//+kubebuilder:object:root=true
|
||||
|
||||
// InfisicalPushSecretList contains a list of InfisicalPushSecret
|
||||
type InfisicalPushSecretList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
Items []InfisicalPushSecret `json:"items"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
SchemeBuilder.Register(&InfisicalPushSecret{}, &InfisicalPushSecretList{})
|
||||
}
|
@ -128,6 +128,128 @@ func (in *GcpIamAuthDetails) DeepCopy() *GcpIamAuthDetails {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InfisicalPushSecret) DeepCopyInto(out *InfisicalPushSecret) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
out.Spec = in.Spec
|
||||
in.Status.DeepCopyInto(&out.Status)
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalPushSecret.
|
||||
func (in *InfisicalPushSecret) DeepCopy() *InfisicalPushSecret {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(InfisicalPushSecret)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *InfisicalPushSecret) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InfisicalPushSecretDestination) DeepCopyInto(out *InfisicalPushSecretDestination) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalPushSecretDestination.
|
||||
func (in *InfisicalPushSecretDestination) DeepCopy() *InfisicalPushSecretDestination {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(InfisicalPushSecretDestination)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InfisicalPushSecretList) DeepCopyInto(out *InfisicalPushSecretList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]InfisicalPushSecret, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalPushSecretList.
|
||||
func (in *InfisicalPushSecretList) DeepCopy() *InfisicalPushSecretList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(InfisicalPushSecretList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *InfisicalPushSecretList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InfisicalPushSecretSpec) DeepCopyInto(out *InfisicalPushSecretSpec) {
|
||||
*out = *in
|
||||
out.Destination = in.Destination
|
||||
out.Authentication = in.Authentication
|
||||
out.Push = in.Push
|
||||
out.TLS = in.TLS
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalPushSecretSpec.
|
||||
func (in *InfisicalPushSecretSpec) DeepCopy() *InfisicalPushSecretSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(InfisicalPushSecretSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InfisicalPushSecretStatus) DeepCopyInto(out *InfisicalPushSecretStatus) {
|
||||
*out = *in
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.ManagedSecrets != nil {
|
||||
in, out := &in.ManagedSecrets, &out.ManagedSecrets
|
||||
*out = make(map[string]string, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new InfisicalPushSecretStatus.
|
||||
func (in *InfisicalPushSecretStatus) DeepCopy() *InfisicalPushSecretStatus {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(InfisicalPushSecretStatus)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *InfisicalSecret) DeepCopyInto(out *InfisicalSecret) {
|
||||
*out = *in
|
||||
@ -332,6 +454,151 @@ func (in *MangedKubeSecretConfig) DeepCopy() *MangedKubeSecretConfig {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretAuthentication) DeepCopyInto(out *PushSecretAuthentication) {
|
||||
*out = *in
|
||||
out.UniversalAuth = in.UniversalAuth
|
||||
out.KubernetesAuth = in.KubernetesAuth
|
||||
out.AwsIamAuth = in.AwsIamAuth
|
||||
out.AzureAuth = in.AzureAuth
|
||||
out.GcpIdTokenAuth = in.GcpIdTokenAuth
|
||||
out.GcpIamAuth = in.GcpIamAuth
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretAuthentication.
|
||||
func (in *PushSecretAuthentication) DeepCopy() *PushSecretAuthentication {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretAuthentication)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretAwsIamAuth) DeepCopyInto(out *PushSecretAwsIamAuth) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretAwsIamAuth.
|
||||
func (in *PushSecretAwsIamAuth) DeepCopy() *PushSecretAwsIamAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretAwsIamAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretAzureAuth) DeepCopyInto(out *PushSecretAzureAuth) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretAzureAuth.
|
||||
func (in *PushSecretAzureAuth) DeepCopy() *PushSecretAzureAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretAzureAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretGcpIamAuth) DeepCopyInto(out *PushSecretGcpIamAuth) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretGcpIamAuth.
|
||||
func (in *PushSecretGcpIamAuth) DeepCopy() *PushSecretGcpIamAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretGcpIamAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretGcpIdTokenAuth) DeepCopyInto(out *PushSecretGcpIdTokenAuth) {
|
||||
*out = *in
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretGcpIdTokenAuth.
|
||||
func (in *PushSecretGcpIdTokenAuth) DeepCopy() *PushSecretGcpIdTokenAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretGcpIdTokenAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretKubernetesAuth) DeepCopyInto(out *PushSecretKubernetesAuth) {
|
||||
*out = *in
|
||||
out.ServiceAccountRef = in.ServiceAccountRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretKubernetesAuth.
|
||||
func (in *PushSecretKubernetesAuth) DeepCopy() *PushSecretKubernetesAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretKubernetesAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretTlsConfig) DeepCopyInto(out *PushSecretTlsConfig) {
|
||||
*out = *in
|
||||
out.CaRef = in.CaRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretTlsConfig.
|
||||
func (in *PushSecretTlsConfig) DeepCopy() *PushSecretTlsConfig {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretTlsConfig)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *PushSecretUniversalAuth) DeepCopyInto(out *PushSecretUniversalAuth) {
|
||||
*out = *in
|
||||
out.CredentialsRef = in.CredentialsRef
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PushSecretUniversalAuth.
|
||||
func (in *PushSecretUniversalAuth) DeepCopy() *PushSecretUniversalAuth {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(PushSecretUniversalAuth)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SecretPush) DeepCopyInto(out *SecretPush) {
|
||||
*out = *in
|
||||
out.Secret = in.Secret
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretPush.
|
||||
func (in *SecretPush) DeepCopy() *SecretPush {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(SecretPush)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *SecretScopeInWorkspace) DeepCopyInto(out *SecretScopeInWorkspace) {
|
||||
*out = *in
|
||||
|
@ -0,0 +1,264 @@
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
creationTimestamp: null
|
||||
name: infisicalpushsecrets.secrets.infisical.com
|
||||
spec:
|
||||
group: secrets.infisical.com
|
||||
names:
|
||||
kind: InfisicalPushSecret
|
||||
listKind: InfisicalPushSecretList
|
||||
plural: infisicalpushsecrets
|
||||
singular: infisicalpushsecret
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: InfisicalPushSecret is the Schema for the infisicalpushsecrets
|
||||
API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation
|
||||
of an object. Servers should convert recognized schemas to the latest
|
||||
internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this
|
||||
object represents. Servers may infer this from the endpoint the client
|
||||
submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: InfisicalPushSecretSpec defines the desired state of InfisicalPushSecret
|
||||
properties:
|
||||
authentication:
|
||||
properties:
|
||||
awsIamAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
type: object
|
||||
azureAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
resource:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
type: object
|
||||
gcpIamAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
serviceAccountKeyFilePath:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
- serviceAccountKeyFilePath
|
||||
type: object
|
||||
gcpIdTokenAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
type: object
|
||||
kubernetesAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
serviceAccountRef:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
required:
|
||||
- identityId
|
||||
- serviceAccountRef
|
||||
type: object
|
||||
universalAuth:
|
||||
description: PushSecretUniversalAuth defines universal authentication
|
||||
properties:
|
||||
credentialsRef:
|
||||
properties:
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The name space where the Kubernetes Secret
|
||||
is located
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
required:
|
||||
- credentialsRef
|
||||
type: object
|
||||
type: object
|
||||
deletionPolicy:
|
||||
type: string
|
||||
destination:
|
||||
properties:
|
||||
environmentSlug:
|
||||
type: string
|
||||
projectId:
|
||||
type: string
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- environmentSlug
|
||||
- projectId
|
||||
- secretsPath
|
||||
type: object
|
||||
hostAPI:
|
||||
description: Infisical host to pull secrets from
|
||||
type: string
|
||||
push:
|
||||
properties:
|
||||
secret:
|
||||
properties:
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The name space where the Kubernetes Secret is
|
||||
located
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
required:
|
||||
- secret
|
||||
type: object
|
||||
resyncInterval:
|
||||
type: string
|
||||
tls:
|
||||
properties:
|
||||
caRef:
|
||||
description: Reference to secret containing CA cert
|
||||
properties:
|
||||
key:
|
||||
description: The name of the secret property with the CA certificate
|
||||
value
|
||||
type: string
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The namespace where the Kubernetes Secret is
|
||||
located
|
||||
type: string
|
||||
required:
|
||||
- key
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
type: object
|
||||
updatePolicy:
|
||||
type: string
|
||||
required:
|
||||
- destination
|
||||
- push
|
||||
- resyncInterval
|
||||
type: object
|
||||
status:
|
||||
description: InfisicalPushSecretStatus defines the observed state of InfisicalPushSecret
|
||||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current
|
||||
state of this API Resource. --- This struct is intended for direct
|
||||
use as an array at the field path .status.conditions. For example,
|
||||
\n type FooStatus struct{ // Represents the observations of a
|
||||
foo's current state. // Known .status.conditions.type are: \"Available\",
|
||||
\"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge
|
||||
// +listType=map // +listMapKey=type Conditions []metav1.Condition
|
||||
`json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\"
|
||||
protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition
|
||||
transitioned from one status to another. This should be when
|
||||
the underlying condition changed. If that is not known, then
|
||||
using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating
|
||||
details about the transition. This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation
|
||||
that the condition was set based upon. For instance, if .metadata.generation
|
||||
is currently 12, but the .status.conditions[x].observedGeneration
|
||||
is 9, the condition is out of date with respect to the current
|
||||
state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating
|
||||
the reason for the condition's last transition. Producers
|
||||
of specific condition types may define expected values and
|
||||
meanings for this field, and whether the values are considered
|
||||
a guaranteed API. The value should be a CamelCase string.
|
||||
This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase.
|
||||
--- Many .condition.type values are consistent across resources
|
||||
like Available, but because arbitrary conditions can be useful
|
||||
(see .node.status.conditions), the ability to deconflict is
|
||||
important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
managedSecrets:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: managed secrets is a map where the key is the ID, and
|
||||
the value is the secret key (string[id], string[key] )
|
||||
type: object
|
||||
required:
|
||||
- conditions
|
||||
- managedSecrets
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
@ -2,7 +2,8 @@
|
||||
# since it depends on service name and namespace that are out of this kustomize package.
|
||||
# It should be run by config/default
|
||||
resources:
|
||||
- bases/secrets.infisical.com_infisicalsecrets.yaml
|
||||
- bases/secrets.infisical.com_infisicalsecrets.yaml
|
||||
- bases/secrets.infisical.com_infisicalpushsecrets.yaml
|
||||
#+kubebuilder:scaffold:crdkustomizeresource
|
||||
|
||||
patchesStrategicMerge:
|
||||
@ -18,4 +19,4 @@ patchesStrategicMerge:
|
||||
|
||||
# the following config is for teaching kustomize how to do kustomization for CRDs.
|
||||
configurations:
|
||||
- kustomizeconfig.yaml
|
||||
- kustomizeconfig.yaml
|
||||
|
27
k8-operator/config/rbac/infisicalpushsecret_editor_role.yaml
Normal file
27
k8-operator/config/rbac/infisicalpushsecret_editor_role.yaml
Normal file
@ -0,0 +1,27 @@
|
||||
# permissions for end users to edit infisicalpushsecrets.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: k8-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: infisicalpushsecret-editor-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicalpushsecrets
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicalpushsecrets/status
|
||||
verbs:
|
||||
- get
|
23
k8-operator/config/rbac/infisicalpushsecret_viewer_role.yaml
Normal file
23
k8-operator/config/rbac/infisicalpushsecret_viewer_role.yaml
Normal file
@ -0,0 +1,23 @@
|
||||
# permissions for end users to view infisicalpushsecrets.
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: k8-operator
|
||||
app.kubernetes.io/managed-by: kustomize
|
||||
name: infisicalpushsecret-viewer-role
|
||||
rules:
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicalpushsecrets
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicalpushsecrets/status
|
||||
verbs:
|
||||
- get
|
@ -44,6 +44,32 @@ rules:
|
||||
- list
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicalpushsecrets
|
||||
verbs:
|
||||
- create
|
||||
- delete
|
||||
- get
|
||||
- list
|
||||
- patch
|
||||
- update
|
||||
- watch
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicalpushsecrets/finalizers
|
||||
verbs:
|
||||
- update
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
- infisicalpushsecrets/status
|
||||
verbs:
|
||||
- get
|
||||
- patch
|
||||
- update
|
||||
- apiGroups:
|
||||
- secrets.infisical.com
|
||||
resources:
|
||||
|
45
k8-operator/config/samples/crd/pushsecret/pushSecret.yaml
Normal file
45
k8-operator/config/samples/crd/pushsecret/pushSecret.yaml
Normal file
@ -0,0 +1,45 @@
|
||||
apiVersion: secrets.infisical.com/v1alpha1
|
||||
kind: InfisicalPushSecret
|
||||
metadata:
|
||||
name: infisical-push-secret-demo
|
||||
spec:
|
||||
resyncInterval: 1m
|
||||
hostAPI: https://app.infisical.com/api
|
||||
|
||||
# Optional, defaults to replacement.
|
||||
updatePolicy: Replace # If set to replace, existing secrets inside Infisical will be replaced by the value of the PushSecret on sync.
|
||||
|
||||
# Optional, defaults to no deletion.
|
||||
deletionPolicy: Delete # If set to delete, the secret(s) inside Infisical managed by the operator, will be deleted if the InfisicalPushSecret CRD is deleted.
|
||||
|
||||
destination:
|
||||
projectId: <project-id>
|
||||
environmentSlug: <env-slug>
|
||||
secretsPath: <secret-path>
|
||||
|
||||
push:
|
||||
secret:
|
||||
secretName: push-secret-demo # Secret CRD
|
||||
secretNamespace: default
|
||||
|
||||
# Only have one authentication method defined or you are likely to run into authentication issues.
|
||||
# Remove all except one authentication method.
|
||||
authentication:
|
||||
awsIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
azureAuth:
|
||||
identityId: <machine-identity-id>
|
||||
gcpIamAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountKeyFilePath: </path-to-service-account-key-file.json>
|
||||
gcpIdTokenAuth:
|
||||
identityId: <machine-identity-id>
|
||||
kubernetesAuth:
|
||||
identityId: <machine-identity-id>
|
||||
serviceAccountRef:
|
||||
name: <secret-name>
|
||||
namespace: <secret-namespace>
|
||||
universalAuth:
|
||||
credentialsRef:
|
||||
secretName: <secret-name> # universal-auth-credentials
|
||||
secretNamespace: <secret-namespace> # default
|
@ -0,0 +1,9 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: push-secret-demo
|
||||
namespace: default
|
||||
stringData: # can also be "data", but needs to be base64 encoded
|
||||
API_KEY: some-api-key
|
||||
DATABASE_URL: postgres://127.0.0.1:5432
|
||||
ENCRYPTION_KEY: fabcc12-a22-facbaa4-11aa568aab
|
156
k8-operator/controllers/infisicalpushsecret/conditions.go
Normal file
156
k8-operator/controllers/infisicalpushsecret/conditions.go
Normal file
@ -0,0 +1,156 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func (r *InfisicalPushSecretReconciler) SetReconcileStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, err error) error {
|
||||
|
||||
if infisicalPushSecret.Status.Conditions == nil {
|
||||
infisicalPushSecret.Status.Conditions = []metav1.Condition{}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/Reconcile",
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: "Error",
|
||||
Message: fmt.Sprintf("Reconcile failed, secrets were not pushed to Infisical. Error: %s", err.Error()),
|
||||
})
|
||||
} else {
|
||||
meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/Reconcile",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "OK",
|
||||
Message: "Reconcile succeeded, secrets were pushed to Infisical",
|
||||
})
|
||||
}
|
||||
|
||||
return r.Client.Status().Update(ctx, infisicalPushSecret)
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalPushSecretReconciler) SetFailedToReplaceSecretsStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error {
|
||||
if infisicalPushSecret.Status.Conditions == nil {
|
||||
infisicalPushSecret.Status.Conditions = []metav1.Condition{}
|
||||
}
|
||||
|
||||
if failMessage != "" {
|
||||
meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/FailedToReplaceSecrets",
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: "Error",
|
||||
Message: failMessage,
|
||||
})
|
||||
} else {
|
||||
meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/FailedToReplaceSecrets",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "OK",
|
||||
Message: "No errors, no secrets failed to be replaced in Infisical",
|
||||
})
|
||||
}
|
||||
|
||||
return r.Client.Status().Update(ctx, infisicalPushSecret)
|
||||
}
|
||||
|
||||
func (r *InfisicalPushSecretReconciler) SetFailedToCreateSecretsStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error {
|
||||
if infisicalPushSecret.Status.Conditions == nil {
|
||||
infisicalPushSecret.Status.Conditions = []metav1.Condition{}
|
||||
}
|
||||
|
||||
if failMessage != "" {
|
||||
meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/FailedToCreateSecrets",
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: "Error",
|
||||
Message: failMessage,
|
||||
})
|
||||
} else {
|
||||
meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/FailedToCreateSecrets",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "OK",
|
||||
Message: "No errors encountered, no secrets failed to be created in Infisical",
|
||||
})
|
||||
}
|
||||
|
||||
return r.Client.Status().Update(ctx, infisicalPushSecret)
|
||||
}
|
||||
|
||||
func (r *InfisicalPushSecretReconciler) SetFailedToUpdateSecretsStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error {
|
||||
if infisicalPushSecret.Status.Conditions == nil {
|
||||
infisicalPushSecret.Status.Conditions = []metav1.Condition{}
|
||||
}
|
||||
|
||||
if failMessage != "" {
|
||||
meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/FailedToUpdateSecrets",
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: "Error",
|
||||
Message: failMessage,
|
||||
})
|
||||
} else {
|
||||
meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/FailedToUpdateSecrets",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "OK",
|
||||
Message: "No errors encountered, no secrets failed to be updated in Infisical",
|
||||
})
|
||||
}
|
||||
|
||||
return r.Client.Status().Update(ctx, infisicalPushSecret)
|
||||
}
|
||||
|
||||
func (r *InfisicalPushSecretReconciler) SetFailedToDeleteSecretsStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, failMessage string) error {
|
||||
if infisicalPushSecret.Status.Conditions == nil {
|
||||
infisicalPushSecret.Status.Conditions = []metav1.Condition{}
|
||||
}
|
||||
|
||||
if failMessage != "" {
|
||||
meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/FailedToDeleteSecrets",
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: "Error",
|
||||
Message: failMessage,
|
||||
})
|
||||
} else {
|
||||
meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/FailedToDeleteSecrets",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "OK",
|
||||
Message: "No errors encountered, no secrets failed to be deleted",
|
||||
})
|
||||
}
|
||||
|
||||
return r.Client.Status().Update(ctx, infisicalPushSecret)
|
||||
}
|
||||
|
||||
func (r *InfisicalPushSecretReconciler) SetAuthenticatedStatusCondition(ctx context.Context, infisicalPushSecret *v1alpha1.InfisicalPushSecret, errorToConditionOn error) error {
|
||||
if infisicalPushSecret.Status.Conditions == nil {
|
||||
infisicalPushSecret.Status.Conditions = []metav1.Condition{}
|
||||
}
|
||||
|
||||
if errorToConditionOn != nil {
|
||||
meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/Authenticated",
|
||||
Status: metav1.ConditionFalse,
|
||||
Reason: "Error",
|
||||
Message: "Failed to authenticate with Infisical API. This can be caused by invalid service token or an invalid API host that is set. Check operator logs for more info",
|
||||
})
|
||||
} else {
|
||||
meta.SetStatusCondition(&infisicalPushSecret.Status.Conditions, metav1.Condition{
|
||||
Type: "secrets.infisical.com/Authenticated",
|
||||
Status: metav1.ConditionTrue,
|
||||
Reason: "OK",
|
||||
Message: "Successfully authenticated with Infisical API",
|
||||
})
|
||||
}
|
||||
|
||||
return r.Client.Status().Update(ctx, infisicalPushSecret)
|
||||
}
|
@ -0,0 +1,249 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/builder"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
|
||||
secretsv1alpha1 "github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/api"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/constants"
|
||||
controllerhelpers "github.com/Infisical/infisical/k8-operator/packages/controllerutil"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/util"
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
// InfisicalSecretReconciler reconciles a InfisicalSecret object
|
||||
type InfisicalPushSecretReconciler struct {
|
||||
client.Client
|
||||
|
||||
BaseLogger logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
var infisicalPushSecretResourceVariablesMap map[string]util.ResourceVariables = make(map[string]util.ResourceVariables)
|
||||
|
||||
func (r *InfisicalPushSecretReconciler) GetLogger(req ctrl.Request) logr.Logger {
|
||||
return r.BaseLogger.WithValues("infisicalpushsecret", req.NamespacedName)
|
||||
}
|
||||
|
||||
//+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalpushsecrets,verbs=get;list;watch;create;update;patch;delete
|
||||
//+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalpushsecrets/status,verbs=get;update;patch
|
||||
//+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalpushsecrets/finalizers,verbs=update
|
||||
//+kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;delete
|
||||
//+kubebuilder:rbac:groups="",resources=configmaps,verbs=get;list;watch;create;update;delete
|
||||
//+kubebuilder:rbac:groups=apps,resources=deployments,verbs=list;watch;get;update
|
||||
//+kubebuilder:rbac:groups="",resources=serviceaccounts,verbs=get;list;watch
|
||||
|
||||
// Reconcile is part of the main kubernetes reconciliation loop which aims to
|
||||
// move the current state of the cluster closer to the desired state.
|
||||
// For more details, check Reconcile and its Result here:
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.1/pkg/reconcile
|
||||
|
||||
func (r *InfisicalPushSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
|
||||
logger := r.GetLogger(req)
|
||||
|
||||
var infisicalPushSecretCRD secretsv1alpha1.InfisicalPushSecret
|
||||
requeueTime := time.Minute // seconds
|
||||
|
||||
err := r.Get(ctx, req.NamespacedName, &infisicalPushSecretCRD)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
logger.Info("Infisical Push Secret CRD not found")
|
||||
return ctrl.Result{
|
||||
Requeue: false,
|
||||
}, nil
|
||||
} else {
|
||||
logger.Error(err, "Unable to fetch Infisical Secret CRD from cluster")
|
||||
return ctrl.Result{
|
||||
RequeueAfter: requeueTime,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Add finalizer if it doesn't exist
|
||||
if !controllerutil.ContainsFinalizer(&infisicalPushSecretCRD, constants.INFISICAL_PUSH_SECRET_FINALIZER_NAME) {
|
||||
controllerutil.AddFinalizer(&infisicalPushSecretCRD, constants.INFISICAL_PUSH_SECRET_FINALIZER_NAME)
|
||||
if err := r.Update(ctx, &infisicalPushSecretCRD); err != nil {
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's being deleted
|
||||
if !infisicalPushSecretCRD.DeletionTimestamp.IsZero() {
|
||||
logger.Info("Handling deletion of InfisicalPushSecret")
|
||||
if controllerutil.ContainsFinalizer(&infisicalPushSecretCRD, constants.INFISICAL_PUSH_SECRET_FINALIZER_NAME) {
|
||||
// We remove finalizers before running deletion logic to be completely safe from stuck resources
|
||||
infisicalPushSecretCRD.ObjectMeta.Finalizers = []string{}
|
||||
if err := r.Update(ctx, &infisicalPushSecretCRD); err != nil {
|
||||
logger.Error(err, fmt.Sprintf("Error removing finalizers from InfisicalPushSecret %s", infisicalPushSecretCRD.Name))
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
if err := r.DeleteManagedSecrets(ctx, logger, infisicalPushSecretCRD); err != nil {
|
||||
return ctrl.Result{}, err // Even if this fails, we still want to delete the CRD
|
||||
}
|
||||
|
||||
}
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
if infisicalPushSecretCRD.Spec.ResyncInterval != "" {
|
||||
|
||||
duration, err := util.ConvertResyncIntervalToDuration(infisicalPushSecretCRD.Spec.ResyncInterval)
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err, fmt.Sprintf("unable to convert resync interval to duration. Will requeue after [requeueTime=%v]", requeueTime))
|
||||
return ctrl.Result{
|
||||
RequeueAfter: requeueTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
requeueTime = duration
|
||||
|
||||
logger.Info(fmt.Sprintf("Manual re-sync interval set. Interval: %v", requeueTime))
|
||||
|
||||
} else {
|
||||
logger.Info(fmt.Sprintf("Re-sync interval set. Interval: %v", requeueTime))
|
||||
}
|
||||
|
||||
// Check if the resource is already marked for deletion
|
||||
if infisicalPushSecretCRD.GetDeletionTimestamp() != nil {
|
||||
return ctrl.Result{
|
||||
Requeue: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get modified/default config
|
||||
infisicalConfig, err := controllerhelpers.GetInfisicalConfigMap(ctx, r.Client)
|
||||
if err != nil {
|
||||
logger.Error(err, fmt.Sprintf("unable to fetch infisical-config. Will requeue after [requeueTime=%v]", requeueTime))
|
||||
return ctrl.Result{
|
||||
RequeueAfter: requeueTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if infisicalPushSecretCRD.Spec.HostAPI == "" {
|
||||
api.API_HOST_URL = infisicalConfig["hostAPI"]
|
||||
} else {
|
||||
api.API_HOST_URL = infisicalPushSecretCRD.Spec.HostAPI
|
||||
}
|
||||
|
||||
if infisicalPushSecretCRD.Spec.TLS.CaRef.SecretName != "" {
|
||||
api.API_CA_CERTIFICATE, err = r.getInfisicalCaCertificateFromKubeSecret(ctx, infisicalPushSecretCRD)
|
||||
if err != nil {
|
||||
logger.Error(err, fmt.Sprintf("unable to fetch CA certificate. Will requeue after [requeueTime=%v]", requeueTime))
|
||||
return ctrl.Result{
|
||||
RequeueAfter: requeueTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
logger.Info("Using custom CA certificate...")
|
||||
} else {
|
||||
api.API_CA_CERTIFICATE = ""
|
||||
}
|
||||
|
||||
err = r.ReconcileInfisicalPushSecret(ctx, logger, infisicalPushSecretCRD)
|
||||
r.SetReconcileStatusCondition(ctx, &infisicalPushSecretCRD, err)
|
||||
|
||||
if err != nil {
|
||||
logger.Error(err, fmt.Sprintf("unable to reconcile Infisical Push Secret. Will requeue after [requeueTime=%v]", requeueTime))
|
||||
return ctrl.Result{
|
||||
RequeueAfter: requeueTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Sync again after the specified time
|
||||
logger.Info(fmt.Sprintf("Operator will requeue after [%v]", requeueTime))
|
||||
return ctrl.Result{
|
||||
RequeueAfter: requeueTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *InfisicalPushSecretReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
|
||||
// Custom predicate that allows both spec changes and deletions
|
||||
specChangeOrDelete := predicate.Funcs{
|
||||
UpdateFunc: func(e event.UpdateEvent) bool {
|
||||
// Only reconcile if spec/generation changed
|
||||
|
||||
isSpecOrGenerationChange := e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration()
|
||||
|
||||
if isSpecOrGenerationChange {
|
||||
if infisicalPushSecretResourceVariablesMap != nil {
|
||||
if rv, ok := infisicalPushSecretResourceVariablesMap[string(e.ObjectNew.GetUID())]; ok {
|
||||
rv.CancelCtx()
|
||||
delete(infisicalPushSecretResourceVariablesMap, string(e.ObjectNew.GetUID()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isSpecOrGenerationChange
|
||||
},
|
||||
DeleteFunc: func(e event.DeleteEvent) bool {
|
||||
// Always reconcile on deletion
|
||||
|
||||
if infisicalPushSecretResourceVariablesMap != nil {
|
||||
if rv, ok := infisicalPushSecretResourceVariablesMap[string(e.Object.GetUID())]; ok {
|
||||
rv.CancelCtx()
|
||||
delete(infisicalPushSecretResourceVariablesMap, string(e.Object.GetUID()))
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
CreateFunc: func(e event.CreateEvent) bool {
|
||||
// Reconcile on creation
|
||||
return true
|
||||
},
|
||||
GenericFunc: func(e event.GenericEvent) bool {
|
||||
// Ignore generic events
|
||||
return false
|
||||
},
|
||||
}
|
||||
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&secretsv1alpha1.InfisicalPushSecret{}, builder.WithPredicates(
|
||||
specChangeOrDelete,
|
||||
)).
|
||||
Watches(
|
||||
&source.Kind{Type: &corev1.Secret{}},
|
||||
handler.EnqueueRequestsFromMapFunc(func(o client.Object) []reconcile.Request {
|
||||
ctx := context.Background()
|
||||
pushSecrets := &secretsv1alpha1.InfisicalPushSecretList{}
|
||||
if err := r.List(ctx, pushSecrets); err != nil {
|
||||
return []reconcile.Request{}
|
||||
}
|
||||
|
||||
requests := []reconcile.Request{}
|
||||
for _, pushSecret := range pushSecrets.Items {
|
||||
if pushSecret.Spec.Push.Secret.SecretName == o.GetName() &&
|
||||
pushSecret.Spec.Push.Secret.SecretNamespace == o.GetNamespace() {
|
||||
requests = append(requests, reconcile.Request{
|
||||
NamespacedName: types.NamespacedName{
|
||||
Name: pushSecret.GetName(),
|
||||
Namespace: pushSecret.GetNamespace(),
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return requests
|
||||
}),
|
||||
).
|
||||
Complete(r)
|
||||
}
|
@ -0,0 +1,482 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/api"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/constants"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/util"
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
infisicalSdk "github.com/infisical/go-sdk"
|
||||
k8Errors "k8s.io/apimachinery/pkg/api/errors"
|
||||
)
|
||||
|
||||
func (r *InfisicalPushSecretReconciler) handleAuthentication(ctx context.Context, infisicalSecret v1alpha1.InfisicalPushSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (util.AuthenticationDetails, error) {
|
||||
authStrategies := map[util.AuthStrategyType]func(ctx context.Context, reconcilerClient client.Client, secretCrd util.SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (util.AuthenticationDetails, error){
|
||||
util.AuthStrategy.UNIVERSAL_MACHINE_IDENTITY: util.HandleUniversalAuth,
|
||||
util.AuthStrategy.KUBERNETES_MACHINE_IDENTITY: util.HandleKubernetesAuth,
|
||||
util.AuthStrategy.AWS_IAM_MACHINE_IDENTITY: util.HandleAwsIamAuth,
|
||||
util.AuthStrategy.AZURE_MACHINE_IDENTITY: util.HandleAzureAuth,
|
||||
util.AuthStrategy.GCP_ID_TOKEN_MACHINE_IDENTITY: util.HandleGcpIdTokenAuth,
|
||||
util.AuthStrategy.GCP_IAM_MACHINE_IDENTITY: util.HandleGcpIamAuth,
|
||||
}
|
||||
|
||||
for authStrategy, authHandler := range authStrategies {
|
||||
authDetails, err := authHandler(ctx, r.Client, util.SecretAuthInput{
|
||||
Secret: infisicalSecret,
|
||||
Type: util.SecretCrd.INFISICAL_PUSH_SECRET,
|
||||
}, infisicalClient)
|
||||
|
||||
if err == nil {
|
||||
return authDetails, nil
|
||||
}
|
||||
|
||||
if !errors.Is(err, util.ErrAuthNotApplicable) {
|
||||
return util.AuthenticationDetails{}, fmt.Errorf("authentication failed for strategy [%s] [err=%w]", authStrategy, err)
|
||||
}
|
||||
}
|
||||
|
||||
return util.AuthenticationDetails{}, fmt.Errorf("no authentication method provided")
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalPushSecretReconciler) getInfisicalCaCertificateFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalPushSecret) (caCertificate string, err error) {
|
||||
|
||||
caCertificateFromKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{
|
||||
Namespace: infisicalSecret.Spec.TLS.CaRef.SecretNamespace,
|
||||
Name: infisicalSecret.Spec.TLS.CaRef.SecretName,
|
||||
})
|
||||
|
||||
if k8Errors.IsNotFound(err) {
|
||||
return "", fmt.Errorf("kubernetes secret containing custom CA certificate cannot be found. [err=%s]", err)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("something went wrong when fetching your CA certificate [err=%s]", err)
|
||||
}
|
||||
|
||||
caCertificateFromSecret := string(caCertificateFromKubeSecret.Data[infisicalSecret.Spec.TLS.CaRef.SecretKey])
|
||||
|
||||
return caCertificateFromSecret, nil
|
||||
}
|
||||
|
||||
func (r *InfisicalPushSecretReconciler) getResourceVariables(infisicalPushSecret v1alpha1.InfisicalPushSecret) util.ResourceVariables {
|
||||
|
||||
var resourceVariables util.ResourceVariables
|
||||
|
||||
if _, ok := infisicalPushSecretResourceVariablesMap[string(infisicalPushSecret.UID)]; !ok {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
client := infisicalSdk.NewInfisicalClient(ctx, infisicalSdk.Config{
|
||||
SiteUrl: api.API_HOST_URL,
|
||||
CaCertificate: api.API_CA_CERTIFICATE,
|
||||
UserAgent: api.USER_AGENT_NAME,
|
||||
})
|
||||
|
||||
infisicalPushSecretResourceVariablesMap[string(infisicalPushSecret.UID)] = util.ResourceVariables{
|
||||
InfisicalClient: client,
|
||||
CancelCtx: cancel,
|
||||
AuthDetails: util.AuthenticationDetails{},
|
||||
}
|
||||
|
||||
resourceVariables = infisicalPushSecretResourceVariablesMap[string(infisicalPushSecret.UID)]
|
||||
|
||||
} else {
|
||||
resourceVariables = infisicalPushSecretResourceVariablesMap[string(infisicalPushSecret.UID)]
|
||||
}
|
||||
|
||||
return resourceVariables
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalPushSecretReconciler) updateResourceVariables(infisicalPushSecret v1alpha1.InfisicalPushSecret, resourceVariables util.ResourceVariables) {
|
||||
infisicalPushSecretResourceVariablesMap[string(infisicalPushSecret.UID)] = resourceVariables
|
||||
}
|
||||
|
||||
func (r *InfisicalPushSecretReconciler) ReconcileInfisicalPushSecret(ctx context.Context, logger logr.Logger, infisicalPushSecret v1alpha1.InfisicalPushSecret) error {
|
||||
|
||||
resourceVariables := r.getResourceVariables(infisicalPushSecret)
|
||||
infisicalClient := resourceVariables.InfisicalClient
|
||||
cancelCtx := resourceVariables.CancelCtx
|
||||
authDetails := resourceVariables.AuthDetails
|
||||
var err error
|
||||
|
||||
if authDetails.AuthStrategy == "" {
|
||||
logger.Info("No authentication strategy found. Attempting to authenticate")
|
||||
authDetails, err = r.handleAuthentication(ctx, infisicalPushSecret, infisicalClient)
|
||||
r.SetAuthenticatedStatusCondition(ctx, &infisicalPushSecret, err)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to authenticate [err=%s]", err)
|
||||
}
|
||||
|
||||
r.updateResourceVariables(infisicalPushSecret, util.ResourceVariables{
|
||||
InfisicalClient: infisicalClient,
|
||||
CancelCtx: cancelCtx,
|
||||
AuthDetails: authDetails,
|
||||
})
|
||||
}
|
||||
|
||||
kubePushSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{
|
||||
Namespace: infisicalPushSecret.Spec.Push.Secret.SecretNamespace,
|
||||
Name: infisicalPushSecret.Spec.Push.Secret.SecretName,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to fetch kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
var kubeSecrets = make(map[string]string)
|
||||
|
||||
for key, value := range kubePushSecret.Data {
|
||||
kubeSecrets[key] = string(value)
|
||||
}
|
||||
|
||||
destination := infisicalPushSecret.Spec.Destination
|
||||
existingSecrets, err := infisicalClient.Secrets().List(infisicalSdk.ListSecretsOptions{
|
||||
ProjectID: destination.ProjectID,
|
||||
Environment: destination.EnvironmentSlug,
|
||||
SecretPath: destination.SecretsPath,
|
||||
IncludeImports: false,
|
||||
})
|
||||
|
||||
getExistingSecretByKey := func(key string) *infisicalSdk.Secret {
|
||||
for _, secret := range existingSecrets {
|
||||
if secret.SecretKey == key {
|
||||
return &secret
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
getExistingSecretById := func(id string) *infisicalSdk.Secret {
|
||||
for _, secret := range existingSecrets {
|
||||
if secret.ID == id {
|
||||
return &secret
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
updateExistingSecretByKey := func(key string, newSecretValue string) {
|
||||
for i := range existingSecrets {
|
||||
if existingSecrets[i].SecretKey == key {
|
||||
existingSecrets[i].SecretValue = newSecretValue
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to list secrets [err=%s]", err)
|
||||
}
|
||||
|
||||
updatePolicy := infisicalPushSecret.Spec.UpdatePolicy
|
||||
|
||||
var secretsFailedToCreate []string
|
||||
var secretsFailedToUpdate []string
|
||||
var secretsFailedToDelete []string
|
||||
var secretsFailedToReplaceById []string
|
||||
|
||||
// If the ManagedSecrets are nil, we know this is the first time the InfisicalPushSecret is being reconciled.
|
||||
if infisicalPushSecret.Status.ManagedSecrets == nil {
|
||||
|
||||
infisicalPushSecret.Status.ManagedSecrets = make(map[string]string) // (string[id], string[key] )
|
||||
|
||||
for secretKey, secretValue := range kubeSecrets {
|
||||
if exists := getExistingSecretByKey(secretKey); exists != nil {
|
||||
|
||||
if updatePolicy == string(constants.PUSH_SECRET_REPLACE_POLICY_ENABLED) {
|
||||
updatedSecret, err := infisicalClient.Secrets().Update(infisicalSdk.UpdateSecretOptions{
|
||||
SecretKey: secretKey,
|
||||
ProjectID: destination.ProjectID,
|
||||
Environment: destination.EnvironmentSlug,
|
||||
SecretPath: destination.SecretsPath,
|
||||
NewSecretValue: secretValue,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
secretsFailedToUpdate = append(secretsFailedToUpdate, secretKey)
|
||||
logger.Info(fmt.Sprintf("unable to update secret [key=%s] [err=%s]", secretKey, err))
|
||||
continue
|
||||
}
|
||||
|
||||
infisicalPushSecret.Status.ManagedSecrets[updatedSecret.ID] = secretKey
|
||||
}
|
||||
} else {
|
||||
createdSecret, err := infisicalClient.Secrets().Create(infisicalSdk.CreateSecretOptions{
|
||||
SecretKey: secretKey,
|
||||
SecretValue: secretValue,
|
||||
ProjectID: destination.ProjectID,
|
||||
Environment: destination.EnvironmentSlug,
|
||||
SecretPath: destination.SecretsPath,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
secretsFailedToCreate = append(secretsFailedToCreate, secretKey)
|
||||
logger.Info(fmt.Sprintf("unable to create secret [key=%s] [err=%s]", secretKey, err))
|
||||
continue
|
||||
}
|
||||
|
||||
infisicalPushSecret.Status.ManagedSecrets[createdSecret.ID] = secretKey
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
// Loop over all the managed secrets, and find the corresponding existingSecret that has the same ID. If the key doesn't match, delete the secret, and re-create it with the correct key/value
|
||||
for managedSecretId, managedSecretKey := range infisicalPushSecret.Status.ManagedSecrets {
|
||||
|
||||
existingSecret := getExistingSecretById(managedSecretId)
|
||||
|
||||
if existingSecret != nil {
|
||||
|
||||
if existingSecret.SecretKey != managedSecretKey {
|
||||
// Secret key has changed, lets delete the secret and re-create it with the correct key
|
||||
|
||||
logger.Info(fmt.Sprintf("Secret with ID [id=%s] has changed key from [%s] to [%s]. Deleting and re-creating secret", managedSecretId, managedSecretKey, existingSecret.SecretKey))
|
||||
|
||||
deletedSecret, err := infisicalClient.Secrets().Delete(infisicalSdk.DeleteSecretOptions{
|
||||
SecretKey: existingSecret.SecretKey,
|
||||
ProjectID: destination.ProjectID,
|
||||
Environment: destination.EnvironmentSlug,
|
||||
SecretPath: destination.SecretsPath,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
secretsFailedToReplaceById = append(secretsFailedToReplaceById, managedSecretKey)
|
||||
logger.Info(fmt.Sprintf("unable to delete secret [key=%s] [err=%s]", managedSecretKey, err))
|
||||
continue
|
||||
}
|
||||
|
||||
createdSecret, err := infisicalClient.Secrets().Create(infisicalSdk.CreateSecretOptions{
|
||||
SecretKey: managedSecretKey,
|
||||
SecretValue: existingSecret.SecretValue,
|
||||
ProjectID: destination.ProjectID,
|
||||
Environment: destination.EnvironmentSlug,
|
||||
SecretPath: destination.SecretsPath,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
secretsFailedToReplaceById = append(secretsFailedToReplaceById, managedSecretKey)
|
||||
logger.Info(fmt.Sprintf("unable to create secret [key=%s] [err=%s]", managedSecretKey, err))
|
||||
continue
|
||||
}
|
||||
|
||||
delete(infisicalPushSecret.Status.ManagedSecrets, deletedSecret.ID)
|
||||
infisicalPushSecret.Status.ManagedSecrets[createdSecret.ID] = managedSecretKey
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// We need to check if any of the secrets have been removed in the new kube secret
|
||||
for _, managedSecretKey := range infisicalPushSecret.Status.ManagedSecrets {
|
||||
|
||||
if _, ok := kubeSecrets[managedSecretKey]; !ok {
|
||||
|
||||
// Secret has been removed, verify that the secret is managed by the operator
|
||||
if getExistingSecretByKey(managedSecretKey) != nil {
|
||||
logger.Info(fmt.Sprintf("Secret with key [key=%s] has been removed from the kube secret. Deleting secret from Infisical", managedSecretKey))
|
||||
|
||||
deletedSecret, err := infisicalClient.Secrets().Delete(infisicalSdk.DeleteSecretOptions{
|
||||
SecretKey: managedSecretKey,
|
||||
ProjectID: destination.ProjectID,
|
||||
Environment: destination.EnvironmentSlug,
|
||||
SecretPath: destination.SecretsPath,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
secretsFailedToDelete = append(secretsFailedToDelete, managedSecretKey)
|
||||
logger.Info(fmt.Sprintf("unable to delete secret [key=%s] [err=%s]", managedSecretKey, err))
|
||||
continue
|
||||
}
|
||||
|
||||
delete(infisicalPushSecret.Status.ManagedSecrets, deletedSecret.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We need to check if any new secrets have been added in the kube secret
|
||||
for currentSecretKey := range kubeSecrets {
|
||||
|
||||
if exists := getExistingSecretByKey(currentSecretKey); exists == nil {
|
||||
|
||||
// Some secrets has been added, verify that the secret that has been added is not already managed by the operator
|
||||
if _, ok := infisicalPushSecret.Status.ManagedSecrets[currentSecretKey]; !ok {
|
||||
|
||||
// Secret was not managed by the operator, lets add it
|
||||
logger.Info(fmt.Sprintf("Secret with key [key=%s] has been added to the kube secret. Creating secret in Infisical", currentSecretKey))
|
||||
|
||||
createdSecret, err := infisicalClient.Secrets().Create(infisicalSdk.CreateSecretOptions{
|
||||
SecretKey: currentSecretKey,
|
||||
SecretValue: kubeSecrets[currentSecretKey],
|
||||
ProjectID: destination.ProjectID,
|
||||
Environment: destination.EnvironmentSlug,
|
||||
SecretPath: destination.SecretsPath,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
secretsFailedToCreate = append(secretsFailedToCreate, currentSecretKey)
|
||||
logger.Info(fmt.Sprintf("unable to create secret [key=%s] [err=%s]", currentSecretKey, err))
|
||||
continue
|
||||
}
|
||||
|
||||
infisicalPushSecret.Status.ManagedSecrets[createdSecret.ID] = currentSecretKey
|
||||
}
|
||||
} else {
|
||||
if updatePolicy == string(constants.PUSH_SECRET_REPLACE_POLICY_ENABLED) {
|
||||
|
||||
existingSecret := getExistingSecretByKey(currentSecretKey)
|
||||
|
||||
if existingSecret != nil && existingSecret.SecretValue != kubeSecrets[currentSecretKey] {
|
||||
logger.Info(fmt.Sprintf("Secret with key [key=%s] has changed value. Updating secret in Infisical", currentSecretKey))
|
||||
|
||||
updatedSecret, err := infisicalClient.Secrets().Update(infisicalSdk.UpdateSecretOptions{
|
||||
SecretKey: currentSecretKey,
|
||||
NewSecretValue: kubeSecrets[currentSecretKey],
|
||||
ProjectID: destination.ProjectID,
|
||||
Environment: destination.EnvironmentSlug,
|
||||
SecretPath: destination.SecretsPath,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
secretsFailedToUpdate = append(secretsFailedToUpdate, currentSecretKey)
|
||||
logger.Info(fmt.Sprintf("unable to update secret [key=%s] [err=%s]", currentSecretKey, err))
|
||||
continue
|
||||
}
|
||||
|
||||
updateExistingSecretByKey(currentSecretKey, kubeSecrets[currentSecretKey])
|
||||
infisicalPushSecret.Status.ManagedSecrets[updatedSecret.ID] = currentSecretKey
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if any of the existing secrets values have changed
|
||||
for secretKey, secretValue := range kubeSecrets {
|
||||
|
||||
existingSecret := getExistingSecretByKey(secretKey)
|
||||
|
||||
if existingSecret != nil {
|
||||
|
||||
_, managedByOperator := infisicalPushSecret.Status.ManagedSecrets[existingSecret.ID]
|
||||
|
||||
if secretValue != existingSecret.SecretValue {
|
||||
|
||||
if managedByOperator || updatePolicy == string(constants.PUSH_SECRET_REPLACE_POLICY_ENABLED) {
|
||||
logger.Info(fmt.Sprintf("Secret with key [key=%s] has changed value. Updating secret in Infisical", secretKey))
|
||||
|
||||
updatedSecret, err := infisicalClient.Secrets().Update(infisicalSdk.UpdateSecretOptions{
|
||||
SecretKey: secretKey,
|
||||
NewSecretValue: secretValue,
|
||||
ProjectID: destination.ProjectID,
|
||||
Environment: destination.EnvironmentSlug,
|
||||
SecretPath: destination.SecretsPath,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
secretsFailedToUpdate = append(secretsFailedToUpdate, secretKey)
|
||||
logger.Info(fmt.Sprintf("unable to update secret [key=%s] [err=%s]", secretKey, err))
|
||||
continue
|
||||
}
|
||||
|
||||
infisicalPushSecret.Status.ManagedSecrets[updatedSecret.ID] = secretKey
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var errorMessage string
|
||||
if len(secretsFailedToCreate) > 0 {
|
||||
errorMessage = fmt.Sprintf("Failed to create secrets: [%s]", strings.Join(secretsFailedToCreate, ", "))
|
||||
} else {
|
||||
errorMessage = ""
|
||||
}
|
||||
r.SetFailedToCreateSecretsStatusCondition(ctx, &infisicalPushSecret, fmt.Sprintf("Failed to create secrets: [%s]", errorMessage))
|
||||
|
||||
if len(secretsFailedToUpdate) > 0 {
|
||||
errorMessage = fmt.Sprintf("Failed to update secrets: [%s]", strings.Join(secretsFailedToUpdate, ", "))
|
||||
} else {
|
||||
errorMessage = ""
|
||||
}
|
||||
r.SetFailedToUpdateSecretsStatusCondition(ctx, &infisicalPushSecret, fmt.Sprintf("Failed to update secrets: [%s]", errorMessage))
|
||||
|
||||
if len(secretsFailedToDelete) > 0 {
|
||||
errorMessage = fmt.Sprintf("Failed to delete secrets: [%s]", strings.Join(secretsFailedToDelete, ", "))
|
||||
} else {
|
||||
errorMessage = ""
|
||||
}
|
||||
r.SetFailedToDeleteSecretsStatusCondition(ctx, &infisicalPushSecret, errorMessage)
|
||||
|
||||
if len(secretsFailedToReplaceById) > 0 {
|
||||
errorMessage = fmt.Sprintf("Failed to replace secrets: [%s]", strings.Join(secretsFailedToReplaceById, ", "))
|
||||
} else {
|
||||
errorMessage = ""
|
||||
}
|
||||
r.SetFailedToReplaceSecretsStatusCondition(ctx, &infisicalPushSecret, errorMessage)
|
||||
|
||||
// Update the status of the InfisicalPushSecret
|
||||
if err := r.Client.Status().Update(ctx, &infisicalPushSecret); err != nil {
|
||||
return fmt.Errorf("unable to update status of InfisicalPushSecret [err=%s]", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalPushSecretReconciler) DeleteManagedSecrets(ctx context.Context, logger logr.Logger, infisicalPushSecret v1alpha1.InfisicalPushSecret) error {
|
||||
if infisicalPushSecret.Spec.DeletionPolicy != string(constants.PUSH_SECRET_DELETE_POLICY_ENABLED) {
|
||||
return nil
|
||||
}
|
||||
|
||||
resourceVariables := r.getResourceVariables(infisicalPushSecret)
|
||||
infisicalClient := resourceVariables.InfisicalClient
|
||||
|
||||
destination := infisicalPushSecret.Spec.Destination
|
||||
existingSecrets, err := infisicalClient.Secrets().List(infisicalSdk.ListSecretsOptions{
|
||||
ProjectID: destination.ProjectID,
|
||||
Environment: destination.EnvironmentSlug,
|
||||
SecretPath: destination.SecretsPath,
|
||||
IncludeImports: false,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to list secrets [err=%s]", err)
|
||||
}
|
||||
|
||||
existingSecretsMappedById := make(map[string]infisicalSdk.Secret)
|
||||
for _, secret := range existingSecrets {
|
||||
existingSecretsMappedById[secret.ID] = secret
|
||||
}
|
||||
|
||||
for managedSecretId, managedSecretKey := range infisicalPushSecret.Status.ManagedSecrets {
|
||||
|
||||
if _, ok := existingSecretsMappedById[managedSecretId]; ok {
|
||||
logger.Info(fmt.Sprintf("Deleting secret with key [key=%s]", managedSecretKey))
|
||||
|
||||
_, err := infisicalClient.Secrets().Delete(infisicalSdk.DeleteSecretOptions{
|
||||
SecretKey: managedSecretKey,
|
||||
ProjectID: destination.ProjectID,
|
||||
Environment: destination.EnvironmentSlug,
|
||||
SecretPath: destination.SecretsPath,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Info(fmt.Sprintf("unable to delete secret [key=%s] [err=%s]", managedSecretKey, err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
@ -6,6 +6,8 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/constants"
|
||||
"github.com/go-logr/logr"
|
||||
v1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
@ -15,7 +17,7 @@ import (
|
||||
const DEPLOYMENT_SECRET_NAME_ANNOTATION_PREFIX = "secrets.infisical.com/managed-secret"
|
||||
const AUTO_RELOAD_DEPLOYMENT_ANNOTATION = "secrets.infisical.com/auto-reload" // needs to be set to true for a deployment to start auto redeploying
|
||||
|
||||
func (r *InfisicalSecretReconciler) ReconcileDeploymentsWithManagedSecrets(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (int, error) {
|
||||
func (r *InfisicalSecretReconciler) ReconcileDeploymentsWithManagedSecrets(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret) (int, error) {
|
||||
listOfDeployments := &v1.DeploymentList{}
|
||||
err := r.Client.List(ctx, listOfDeployments, &client.ListOptions{Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace})
|
||||
if err != nil {
|
||||
@ -42,8 +44,8 @@ func (r *InfisicalSecretReconciler) ReconcileDeploymentsWithManagedSecrets(ctx c
|
||||
wg.Add(1)
|
||||
go func(d v1.Deployment, s corev1.Secret) {
|
||||
defer wg.Done()
|
||||
if err := r.ReconcileDeployment(ctx, d, s); err != nil {
|
||||
fmt.Printf("unable to reconcile deployment with [name=%v]. Will try next requeue", deployment.ObjectMeta.Name)
|
||||
if err := r.ReconcileDeployment(ctx, logger, d, s); err != nil {
|
||||
logger.Error(err, fmt.Sprintf("unable to reconcile deployment with [name=%v]. Will try next requeue", deployment.ObjectMeta.Name))
|
||||
}
|
||||
}(deployment, *managedKubeSecret)
|
||||
}
|
||||
@ -80,17 +82,17 @@ func (r *InfisicalSecretReconciler) IsDeploymentUsingManagedSecret(deployment v1
|
||||
|
||||
// This function ensures that a deployment is in sync with a Kubernetes secret by comparing their versions.
|
||||
// If the version of the secret is different from the version annotation on the deployment, the annotation is updated to trigger a restart of the deployment.
|
||||
func (r *InfisicalSecretReconciler) ReconcileDeployment(ctx context.Context, deployment v1.Deployment, secret corev1.Secret) error {
|
||||
func (r *InfisicalSecretReconciler) ReconcileDeployment(ctx context.Context, logger logr.Logger, deployment v1.Deployment, secret corev1.Secret) error {
|
||||
annotationKey := fmt.Sprintf("%s.%s", DEPLOYMENT_SECRET_NAME_ANNOTATION_PREFIX, secret.Name)
|
||||
annotationValue := secret.Annotations[SECRET_VERSION_ANNOTATION]
|
||||
annotationValue := secret.Annotations[constants.SECRET_VERSION_ANNOTATION]
|
||||
|
||||
if deployment.Annotations[annotationKey] == annotationValue &&
|
||||
deployment.Spec.Template.Annotations[annotationKey] == annotationValue {
|
||||
fmt.Printf("The [deploymentName=%v] is already using the most up to date managed secrets. No action required.\n", deployment.ObjectMeta.Name)
|
||||
logger.Info(fmt.Sprintf("The [deploymentName=%v] is already using the most up to date managed secrets. No action required.", deployment.ObjectMeta.Name))
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Printf("deployment is using outdated managed secret. Starting re-deployment [deploymentName=%v]\n", deployment.ObjectMeta.Name)
|
||||
logger.Info(fmt.Sprintf("Deployment is using outdated managed secret. Starting re-deployment [deploymentName=%v]", deployment.ObjectMeta.Name))
|
||||
|
||||
if deployment.Spec.Template.Annotations == nil {
|
||||
deployment.Spec.Template.Annotations = make(map[string]string)
|
@ -5,6 +5,8 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/util"
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
@ -40,7 +42,7 @@ func (r *InfisicalSecretReconciler) SetReadyToSyncSecretsConditions(ctx context.
|
||||
return r.Client.Status().Update(ctx, infisicalSecret)
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, authStrategy AuthStrategyType, errorToConditionOn error) {
|
||||
func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.Context, logger logr.Logger, infisicalSecret *v1alpha1.InfisicalSecret, authStrategy util.AuthStrategyType, errorToConditionOn error) {
|
||||
if infisicalSecret.Status.Conditions == nil {
|
||||
infisicalSecret.Status.Conditions = []metav1.Condition{}
|
||||
}
|
||||
@ -63,11 +65,11 @@ func (r *InfisicalSecretReconciler) SetInfisicalTokenLoadCondition(ctx context.C
|
||||
|
||||
err := r.Client.Status().Update(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
fmt.Println("Could not set condition for LoadedInfisicalToken")
|
||||
logger.Error(err, "Could not set condition for LoadedInfisicalToken")
|
||||
}
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) SetInfisicalAutoRedeploymentReady(ctx context.Context, infisicalSecret *v1alpha1.InfisicalSecret, numDeployments int, errorToConditionOn error) {
|
||||
func (r *InfisicalSecretReconciler) SetInfisicalAutoRedeploymentReady(ctx context.Context, logger logr.Logger, infisicalSecret *v1alpha1.InfisicalSecret, numDeployments int, errorToConditionOn error) {
|
||||
if infisicalSecret.Status.Conditions == nil {
|
||||
infisicalSecret.Status.Conditions = []metav1.Condition{}
|
||||
}
|
||||
@ -90,6 +92,6 @@ func (r *InfisicalSecretReconciler) SetInfisicalAutoRedeploymentReady(ctx contex
|
||||
|
||||
err := r.Client.Status().Update(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
fmt.Println("Could not set condition for AutoRedeployReady")
|
||||
logger.Error(err, "Could not set condition for AutoRedeployReady")
|
||||
}
|
||||
}
|
@ -15,13 +15,24 @@ import (
|
||||
|
||||
secretsv1alpha1 "github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/api"
|
||||
infisicalSdk "github.com/infisical/go-sdk"
|
||||
controllerhelpers "github.com/Infisical/infisical/k8-operator/packages/controllerutil"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/util"
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
||||
// InfisicalSecretReconciler reconciles a InfisicalSecret object
|
||||
type InfisicalSecretReconciler struct {
|
||||
client.Client
|
||||
Scheme *runtime.Scheme
|
||||
BaseLogger logr.Logger
|
||||
Scheme *runtime.Scheme
|
||||
}
|
||||
|
||||
const FINALIZER_NAME = "secrets.finalizers.infisical.com"
|
||||
|
||||
var infisicalSecretResourceVariablesMap map[string]util.ResourceVariables = make(map[string]util.ResourceVariables)
|
||||
|
||||
func (r *InfisicalSecretReconciler) GetLogger(req ctrl.Request) logr.Logger {
|
||||
return r.BaseLogger.WithValues("infisicalsecret", req.NamespacedName)
|
||||
}
|
||||
|
||||
//+kubebuilder:rbac:groups=secrets.infisical.com,resources=infisicalsecrets,verbs=get;list;watch;create;update;patch;delete
|
||||
@ -37,29 +48,21 @@ type InfisicalSecretReconciler struct {
|
||||
// For more details, check Reconcile and its Result here:
|
||||
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.1/pkg/reconcile
|
||||
|
||||
type ResourceVariables struct {
|
||||
infisicalClient infisicalSdk.InfisicalClientInterface
|
||||
cancelCtx context.CancelFunc
|
||||
authDetails AuthenticationDetails
|
||||
}
|
||||
|
||||
const FINALIZER_NAME = "secrets.finalizers.infisical.com"
|
||||
|
||||
// Maps the infisicalSecretCR.UID to a infisicalSdk.InfisicalClientInterface and AuthenticationDetails.
|
||||
var resourceVariablesMap = make(map[string]ResourceVariables)
|
||||
|
||||
func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
var infisicalSecretCR secretsv1alpha1.InfisicalSecret
|
||||
|
||||
logger := r.GetLogger(req)
|
||||
|
||||
var infisicalSecretCRD secretsv1alpha1.InfisicalSecret
|
||||
requeueTime := time.Minute // seconds
|
||||
|
||||
err := r.Get(ctx, req.NamespacedName, &infisicalSecretCR)
|
||||
err := r.Get(ctx, req.NamespacedName, &infisicalSecretCRD)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return ctrl.Result{
|
||||
Requeue: false,
|
||||
}, nil
|
||||
} else {
|
||||
fmt.Printf("\nUnable to fetch Infisical Secret CRD from cluster because [err=%v]", err)
|
||||
logger.Error(err, "unable to fetch Infisical Secret CRD from cluster")
|
||||
return ctrl.Result{
|
||||
RequeueAfter: requeueTime,
|
||||
}, nil
|
||||
@ -68,80 +71,82 @@ func (r *InfisicalSecretReconciler) Reconcile(ctx context.Context, req ctrl.Requ
|
||||
|
||||
// Remove finalizers if they exist. This is to support previous InfisicalSecret CRD's that have finalizers on them.
|
||||
// In order to delete secrets with finalizers, we first remove the finalizers so we can use the simplified and improved deletion process
|
||||
if !infisicalSecretCR.ObjectMeta.DeletionTimestamp.IsZero() && len(infisicalSecretCR.ObjectMeta.Finalizers) > 0 {
|
||||
infisicalSecretCR.ObjectMeta.Finalizers = []string{}
|
||||
if err := r.Update(ctx, &infisicalSecretCR); err != nil {
|
||||
fmt.Printf("Error removing finalizers from Infisical Secret %s: %v\n", infisicalSecretCR.Name, err)
|
||||
if !infisicalSecretCRD.ObjectMeta.DeletionTimestamp.IsZero() && len(infisicalSecretCRD.ObjectMeta.Finalizers) > 0 {
|
||||
infisicalSecretCRD.ObjectMeta.Finalizers = []string{}
|
||||
if err := r.Update(ctx, &infisicalSecretCRD); err != nil {
|
||||
logger.Error(err, fmt.Sprintf("Error removing finalizers from Infisical Secret %s", infisicalSecretCRD.Name))
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
// Our finalizers have been removed, so the reconciler can do nothing.
|
||||
return ctrl.Result{}, nil
|
||||
}
|
||||
|
||||
if infisicalSecretCR.Spec.ResyncInterval != 0 {
|
||||
requeueTime = time.Second * time.Duration(infisicalSecretCR.Spec.ResyncInterval)
|
||||
fmt.Printf("\nManual re-sync interval set. Interval: %v\n", requeueTime)
|
||||
if infisicalSecretCRD.Spec.ResyncInterval != 0 {
|
||||
requeueTime = time.Second * time.Duration(infisicalSecretCRD.Spec.ResyncInterval)
|
||||
logger.Info(fmt.Sprintf("Manual re-sync interval set. Interval: %v", requeueTime))
|
||||
|
||||
} else {
|
||||
fmt.Printf("\nRe-sync interval set. Interval: %v\n", requeueTime)
|
||||
logger.Info(fmt.Sprintf("Re-sync interval set. Interval: %v", requeueTime))
|
||||
}
|
||||
|
||||
// Check if the resource is already marked for deletion
|
||||
if infisicalSecretCR.GetDeletionTimestamp() != nil {
|
||||
if infisicalSecretCRD.GetDeletionTimestamp() != nil {
|
||||
return ctrl.Result{
|
||||
Requeue: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get modified/default config
|
||||
infisicalConfig, err := r.GetInfisicalConfigMap(ctx)
|
||||
infisicalConfig, err := controllerhelpers.GetInfisicalConfigMap(ctx, r.Client)
|
||||
if err != nil {
|
||||
fmt.Printf("unable to fetch infisical-config [err=%s]. Will requeue after [requeueTime=%v]\n", err, requeueTime)
|
||||
logger.Error(err, fmt.Sprintf("unable to fetch infisical-config. Will requeue after [requeueTime=%v]", requeueTime))
|
||||
return ctrl.Result{
|
||||
RequeueAfter: requeueTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
if infisicalSecretCR.Spec.HostAPI == "" {
|
||||
if infisicalSecretCRD.Spec.HostAPI == "" {
|
||||
api.API_HOST_URL = infisicalConfig["hostAPI"]
|
||||
} else {
|
||||
api.API_HOST_URL = infisicalSecretCR.Spec.HostAPI
|
||||
api.API_HOST_URL = infisicalSecretCRD.Spec.HostAPI
|
||||
}
|
||||
|
||||
if infisicalSecretCR.Spec.TLS.CaRef.SecretName != "" {
|
||||
api.API_CA_CERTIFICATE, err = r.GetInfisicalCaCertificateFromKubeSecret(ctx, infisicalSecretCR)
|
||||
if infisicalSecretCRD.Spec.TLS.CaRef.SecretName != "" {
|
||||
api.API_CA_CERTIFICATE, err = r.getInfisicalCaCertificateFromKubeSecret(ctx, infisicalSecretCRD)
|
||||
if err != nil {
|
||||
fmt.Printf("unable to fetch CA certificate [err=%s]. Will requeue after [requeueTime=%v]\n", err, requeueTime)
|
||||
logger.Error(err, fmt.Sprintf("unable to fetch CA certificate. Will requeue after [requeueTime=%v]", requeueTime))
|
||||
return ctrl.Result{
|
||||
RequeueAfter: requeueTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
fmt.Println("Using custom CA certificate...")
|
||||
logger.Info("Using custom CA certificate...")
|
||||
} else {
|
||||
api.API_CA_CERTIFICATE = ""
|
||||
}
|
||||
|
||||
err = r.ReconcileInfisicalSecret(ctx, infisicalSecretCR)
|
||||
r.SetReadyToSyncSecretsConditions(ctx, &infisicalSecretCR, err)
|
||||
err = r.ReconcileInfisicalSecret(ctx, logger, infisicalSecretCRD)
|
||||
r.SetReadyToSyncSecretsConditions(ctx, &infisicalSecretCRD, err)
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("unable to reconcile Infisical Secret because [err=%v]. Will requeue after [requeueTime=%v]\n", err, requeueTime)
|
||||
|
||||
logger.Error(err, fmt.Sprintf("unable to reconcile InfisicalSecret. Will requeue after [requeueTime=%v]", requeueTime))
|
||||
return ctrl.Result{
|
||||
RequeueAfter: requeueTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
numDeployments, err := r.ReconcileDeploymentsWithManagedSecrets(ctx, infisicalSecretCR)
|
||||
r.SetInfisicalAutoRedeploymentReady(ctx, &infisicalSecretCR, numDeployments, err)
|
||||
numDeployments, err := r.ReconcileDeploymentsWithManagedSecrets(ctx, logger, infisicalSecretCRD)
|
||||
r.SetInfisicalAutoRedeploymentReady(ctx, logger, &infisicalSecretCRD, numDeployments, err)
|
||||
if err != nil {
|
||||
fmt.Printf("unable to reconcile auto redeployment because [err=%v]", err)
|
||||
logger.Error(err, fmt.Sprintf("unable to reconcile auto redeployment. Will requeue after [requeueTime=%v]", requeueTime))
|
||||
return ctrl.Result{
|
||||
RequeueAfter: requeueTime,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Sync again after the specified time
|
||||
fmt.Printf("Operator will requeue after [%v] \n", requeueTime)
|
||||
logger.Info(fmt.Sprintf("Operator will requeue after [%v]", requeueTime))
|
||||
return ctrl.Result{
|
||||
RequeueAfter: requeueTime,
|
||||
}, nil
|
||||
@ -151,16 +156,20 @@ func (r *InfisicalSecretReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
For(&secretsv1alpha1.InfisicalSecret{}, builder.WithPredicates(predicate.Funcs{
|
||||
UpdateFunc: func(e event.UpdateEvent) bool {
|
||||
if rv, ok := resourceVariablesMap[string(e.ObjectNew.GetUID())]; ok {
|
||||
rv.cancelCtx()
|
||||
delete(resourceVariablesMap, string(e.ObjectNew.GetUID()))
|
||||
if infisicalSecretResourceVariablesMap != nil {
|
||||
if rv, ok := infisicalSecretResourceVariablesMap[string(e.ObjectNew.GetUID())]; ok {
|
||||
rv.CancelCtx()
|
||||
delete(infisicalSecretResourceVariablesMap, string(e.ObjectNew.GetUID()))
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
DeleteFunc: func(e event.DeleteEvent) bool {
|
||||
if rv, ok := resourceVariablesMap[string(e.Object.GetUID())]; ok {
|
||||
rv.cancelCtx()
|
||||
delete(resourceVariablesMap, string(e.Object.GetUID()))
|
||||
if infisicalSecretResourceVariablesMap != nil {
|
||||
if rv, ok := infisicalSecretResourceVariablesMap[string(e.Object.GetUID())]; ok {
|
||||
rv.CancelCtx()
|
||||
delete(infisicalSecretResourceVariablesMap, string(e.Object.GetUID()))
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
@ -10,8 +10,10 @@ import (
|
||||
|
||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/api"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/constants"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/model"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/util"
|
||||
"github.com/go-logr/logr"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
@ -20,113 +22,61 @@ import (
|
||||
k8Errors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
ctrl "sigs.k8s.io/controller-runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
const SERVICE_ACCOUNT_ACCESS_KEY = "serviceAccountAccessKey"
|
||||
const SERVICE_ACCOUNT_PUBLIC_KEY = "serviceAccountPublicKey"
|
||||
const SERVICE_ACCOUNT_PRIVATE_KEY = "serviceAccountPrivateKey"
|
||||
|
||||
const INFISICAL_MACHINE_IDENTITY_CLIENT_ID = "clientId"
|
||||
const INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET = "clientSecret"
|
||||
|
||||
const INFISICAL_TOKEN_SECRET_KEY_NAME = "infisicalToken"
|
||||
const SECRET_VERSION_ANNOTATION = "secrets.infisical.com/version" // used to set the version of secrets via Etag
|
||||
const OPERATOR_SETTINGS_CONFIGMAP_NAME = "infisical-config"
|
||||
const OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE = "infisical-operator-system"
|
||||
const INFISICAL_DOMAIN = "https://app.infisical.com/api"
|
||||
|
||||
func (r *InfisicalSecretReconciler) HandleAuthentication(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
func (r *InfisicalSecretReconciler) handleAuthentication(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (util.AuthenticationDetails, error) {
|
||||
|
||||
// ? Legacy support, service token auth
|
||||
infisicalToken, err := r.GetInfisicalTokenFromKubeSecret(ctx, infisicalSecret)
|
||||
infisicalToken, err := r.getInfisicalTokenFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err)
|
||||
return util.AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err)
|
||||
}
|
||||
if infisicalToken != "" {
|
||||
infisicalClient.Auth().SetAccessToken(infisicalToken)
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.SERVICE_TOKEN}, nil
|
||||
return util.AuthenticationDetails{AuthStrategy: util.AuthStrategy.SERVICE_TOKEN}, nil
|
||||
}
|
||||
|
||||
// ? Legacy support, service account auth
|
||||
serviceAccountCreds, err := r.GetInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
|
||||
serviceAccountCreds, err := r.getInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err)
|
||||
return util.AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
if serviceAccountCreds.AccessKey != "" || serviceAccountCreds.PrivateKey != "" || serviceAccountCreds.PublicKey != "" {
|
||||
infisicalClient.Auth().SetAccessToken(serviceAccountCreds.AccessKey)
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.SERVICE_ACCOUNT}, nil
|
||||
return util.AuthenticationDetails{AuthStrategy: util.AuthStrategy.SERVICE_ACCOUNT}, nil
|
||||
}
|
||||
|
||||
authStrategies := map[AuthStrategyType]func(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error){
|
||||
AuthStrategy.UNIVERSAL_MACHINE_IDENTITY: r.handleUniversalAuth,
|
||||
AuthStrategy.KUBERNETES_MACHINE_IDENTITY: r.handleKubernetesAuth,
|
||||
AuthStrategy.AWS_IAM_MACHINE_IDENTITY: r.handleAwsIamAuth,
|
||||
AuthStrategy.AZURE_MACHINE_IDENTITY: r.handleAzureAuth,
|
||||
AuthStrategy.GCP_ID_TOKEN_MACHINE_IDENTITY: r.handleGcpIdTokenAuth,
|
||||
AuthStrategy.GCP_IAM_MACHINE_IDENTITY: r.handleGcpIamAuth,
|
||||
authStrategies := map[util.AuthStrategyType]func(ctx context.Context, reconcilerClient client.Client, secretCrd util.SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (util.AuthenticationDetails, error){
|
||||
util.AuthStrategy.UNIVERSAL_MACHINE_IDENTITY: util.HandleUniversalAuth,
|
||||
util.AuthStrategy.KUBERNETES_MACHINE_IDENTITY: util.HandleKubernetesAuth,
|
||||
util.AuthStrategy.AWS_IAM_MACHINE_IDENTITY: util.HandleAwsIamAuth,
|
||||
util.AuthStrategy.AZURE_MACHINE_IDENTITY: util.HandleAzureAuth,
|
||||
util.AuthStrategy.GCP_ID_TOKEN_MACHINE_IDENTITY: util.HandleGcpIdTokenAuth,
|
||||
util.AuthStrategy.GCP_IAM_MACHINE_IDENTITY: util.HandleGcpIamAuth,
|
||||
}
|
||||
|
||||
for authStrategy, authHandler := range authStrategies {
|
||||
authDetails, err := authHandler(ctx, infisicalSecret, infisicalClient)
|
||||
authDetails, err := authHandler(ctx, r.Client, util.SecretAuthInput{
|
||||
Secret: infisicalSecret,
|
||||
Type: util.SecretCrd.INFISICAL_SECRET,
|
||||
}, infisicalClient)
|
||||
|
||||
if err == nil {
|
||||
return authDetails, nil
|
||||
}
|
||||
|
||||
if !errors.Is(err, ErrAuthNotApplicable) {
|
||||
return AuthenticationDetails{}, fmt.Errorf("authentication failed for strategy [%s] [err=%w]", authStrategy, err)
|
||||
if !errors.Is(err, util.ErrAuthNotApplicable) {
|
||||
return util.AuthenticationDetails{}, fmt.Errorf("authentication failed for strategy [%s] [err=%w]", authStrategy, err)
|
||||
}
|
||||
}
|
||||
|
||||
return AuthenticationDetails{}, fmt.Errorf("no authentication method provided")
|
||||
return util.AuthenticationDetails{}, fmt.Errorf("no authentication method provided")
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) GetInfisicalConfigMap(ctx context.Context) (configMap map[string]string, errToReturn error) {
|
||||
// default key values
|
||||
defaultConfigMapData := make(map[string]string)
|
||||
defaultConfigMapData["hostAPI"] = INFISICAL_DOMAIN
|
||||
|
||||
kubeConfigMap := &corev1.ConfigMap{}
|
||||
err := r.Client.Get(ctx, types.NamespacedName{
|
||||
Namespace: OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE,
|
||||
Name: OPERATOR_SETTINGS_CONFIGMAP_NAME,
|
||||
}, kubeConfigMap)
|
||||
|
||||
if err != nil {
|
||||
if k8Errors.IsNotFound(err) {
|
||||
kubeConfigMap = nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("GetConfigMapByNamespacedName: unable to fetch config map in [namespacedName=%s] [err=%s]", OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE, err)
|
||||
}
|
||||
}
|
||||
|
||||
if kubeConfigMap == nil {
|
||||
return defaultConfigMapData, nil
|
||||
} else {
|
||||
for key, value := range defaultConfigMapData {
|
||||
_, exists := kubeConfigMap.Data[key]
|
||||
if !exists {
|
||||
kubeConfigMap.Data[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return kubeConfigMap.Data, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) GetKubeSecretByNamespacedName(ctx context.Context, namespacedName types.NamespacedName) (*corev1.Secret, error) {
|
||||
kubeSecret := &corev1.Secret{}
|
||||
err := r.Client.Get(ctx, namespacedName, kubeSecret)
|
||||
if err != nil {
|
||||
kubeSecret = nil
|
||||
}
|
||||
|
||||
return kubeSecret, err
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) GetInfisicalTokenFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (string, error) {
|
||||
func (r *InfisicalSecretReconciler) getInfisicalTokenFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (string, error) {
|
||||
// default to new secret ref structure
|
||||
secretName := infisicalSecret.Spec.Authentication.ServiceToken.ServiceTokenSecretReference.SecretName
|
||||
secretNamespace := infisicalSecret.Spec.Authentication.ServiceToken.ServiceTokenSecretReference.SecretNamespace
|
||||
@ -139,7 +89,7 @@ func (r *InfisicalSecretReconciler) GetInfisicalTokenFromKubeSecret(ctx context.
|
||||
secretNamespace = infisicalSecret.Spec.TokenSecretReference.SecretNamespace
|
||||
}
|
||||
|
||||
tokenSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
|
||||
tokenSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{
|
||||
Namespace: secretNamespace,
|
||||
Name: secretName,
|
||||
})
|
||||
@ -152,36 +102,14 @@ func (r *InfisicalSecretReconciler) GetInfisicalTokenFromKubeSecret(ctx context.
|
||||
return "", fmt.Errorf("failed to read Infisical token secret from secret named [%s] in namespace [%s]: with error [%w]", infisicalSecret.Spec.TokenSecretReference.SecretName, infisicalSecret.Spec.TokenSecretReference.SecretNamespace, err)
|
||||
}
|
||||
|
||||
infisicalServiceToken := tokenSecret.Data[INFISICAL_TOKEN_SECRET_KEY_NAME]
|
||||
infisicalServiceToken := tokenSecret.Data[constants.INFISICAL_TOKEN_SECRET_KEY_NAME]
|
||||
|
||||
return strings.Replace(string(infisicalServiceToken), " ", "", -1), nil
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) GetInfisicalUniversalAuthFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (machineIdentityDetails model.MachineIdentityDetails, err error) {
|
||||
func (r *InfisicalSecretReconciler) getInfisicalCaCertificateFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (caCertificate string, err error) {
|
||||
|
||||
universalAuthCredsFromKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
|
||||
Namespace: infisicalSecret.Spec.Authentication.UniversalAuth.CredentialsRef.SecretNamespace,
|
||||
Name: infisicalSecret.Spec.Authentication.UniversalAuth.CredentialsRef.SecretName,
|
||||
})
|
||||
|
||||
if k8Errors.IsNotFound(err) {
|
||||
return model.MachineIdentityDetails{}, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return model.MachineIdentityDetails{}, fmt.Errorf("something went wrong when fetching your machine identity credentials [err=%s]", err)
|
||||
}
|
||||
|
||||
clientIdFromSecret := universalAuthCredsFromKubeSecret.Data[INFISICAL_MACHINE_IDENTITY_CLIENT_ID]
|
||||
clientSecretFromSecret := universalAuthCredsFromKubeSecret.Data[INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET]
|
||||
|
||||
return model.MachineIdentityDetails{ClientId: string(clientIdFromSecret), ClientSecret: string(clientSecretFromSecret)}, nil
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) GetInfisicalCaCertificateFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (caCertificate string, err error) {
|
||||
|
||||
caCertificateFromKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
|
||||
caCertificateFromKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{
|
||||
Namespace: infisicalSecret.Spec.TLS.CaRef.SecretNamespace,
|
||||
Name: infisicalSecret.Spec.TLS.CaRef.SecretName,
|
||||
})
|
||||
@ -197,13 +125,12 @@ func (r *InfisicalSecretReconciler) GetInfisicalCaCertificateFromKubeSecret(ctx
|
||||
caCertificateFromSecret := string(caCertificateFromKubeSecret.Data[infisicalSecret.Spec.TLS.CaRef.SecretKey])
|
||||
|
||||
return caCertificateFromSecret, nil
|
||||
|
||||
}
|
||||
|
||||
// Fetches service account credentials from a Kubernetes secret specified in the infisicalSecret object, extracts the access key, public key, and private key from the secret, and returns them as a ServiceAccountCredentials object.
|
||||
// If any keys are missing or an error occurs, returns an empty object or an error object, respectively.
|
||||
func (r *InfisicalSecretReconciler) GetInfisicalServiceAccountCredentialsFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (serviceAccountDetails model.ServiceAccountDetails, err error) {
|
||||
serviceAccountCredsFromKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
|
||||
func (r *InfisicalSecretReconciler) getInfisicalServiceAccountCredentialsFromKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) (serviceAccountDetails model.ServiceAccountDetails, err error) {
|
||||
serviceAccountCredsFromKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{
|
||||
Namespace: infisicalSecret.Spec.Authentication.ServiceAccount.ServiceAccountSecretReference.SecretNamespace,
|
||||
Name: infisicalSecret.Spec.Authentication.ServiceAccount.ServiceAccountSecretReference.SecretName,
|
||||
})
|
||||
@ -216,9 +143,9 @@ func (r *InfisicalSecretReconciler) GetInfisicalServiceAccountCredentialsFromKub
|
||||
return model.ServiceAccountDetails{}, fmt.Errorf("something went wrong when fetching your service account credentials [err=%s]", err)
|
||||
}
|
||||
|
||||
accessKeyFromSecret := serviceAccountCredsFromKubeSecret.Data[SERVICE_ACCOUNT_ACCESS_KEY]
|
||||
publicKeyFromSecret := serviceAccountCredsFromKubeSecret.Data[SERVICE_ACCOUNT_PUBLIC_KEY]
|
||||
privateKeyFromSecret := serviceAccountCredsFromKubeSecret.Data[SERVICE_ACCOUNT_PRIVATE_KEY]
|
||||
accessKeyFromSecret := serviceAccountCredsFromKubeSecret.Data[constants.SERVICE_ACCOUNT_ACCESS_KEY]
|
||||
publicKeyFromSecret := serviceAccountCredsFromKubeSecret.Data[constants.SERVICE_ACCOUNT_PUBLIC_KEY]
|
||||
privateKeyFromSecret := serviceAccountCredsFromKubeSecret.Data[constants.SERVICE_ACCOUNT_PRIVATE_KEY]
|
||||
|
||||
if accessKeyFromSecret == nil || publicKeyFromSecret == nil || privateKeyFromSecret == nil {
|
||||
return model.ServiceAccountDetails{}, nil
|
||||
@ -227,7 +154,7 @@ func (r *InfisicalSecretReconciler) GetInfisicalServiceAccountCredentialsFromKub
|
||||
return model.ServiceAccountDetails{AccessKey: string(accessKeyFromSecret), PrivateKey: string(privateKeyFromSecret), PublicKey: string(publicKeyFromSecret)}, nil
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
|
||||
func (r *InfisicalSecretReconciler) createInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
|
||||
plainProcessedSecrets := make(map[string][]byte)
|
||||
secretType := infisicalSecret.Spec.ManagedSecretReference.SecretType
|
||||
managedTemplateData := infisicalSecret.Spec.ManagedSecretReference.Template
|
||||
@ -283,7 +210,7 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context
|
||||
}
|
||||
}
|
||||
|
||||
annotations[SECRET_VERSION_ANNOTATION] = ETag
|
||||
annotations[constants.SECRET_VERSION_ANNOTATION] = ETag
|
||||
|
||||
// create a new secret as specified by the managed secret spec of CRD
|
||||
newKubeSecretInstance := &corev1.Secret{
|
||||
@ -310,11 +237,11 @@ func (r *InfisicalSecretReconciler) CreateInfisicalManagedKubeSecret(ctx context
|
||||
return fmt.Errorf("unable to create the managed Kubernetes secret : %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully created a managed Kubernetes secret with your Infisical secrets. Type: %s\n", secretType)
|
||||
logger.Info(fmt.Sprintf("Successfully created a managed Kubernetes secret with your Infisical secrets. Type: %s", secretType))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, managedKubeSecret corev1.Secret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
|
||||
func (r *InfisicalSecretReconciler) updateInfisicalManagedKubeSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret, managedKubeSecret corev1.Secret, secretsFromAPI []model.SingleEnvironmentVariable, ETag string) error {
|
||||
managedTemplateData := infisicalSecret.Spec.ManagedSecretReference.Template
|
||||
|
||||
plainProcessedSecrets := make(map[string][]byte)
|
||||
@ -354,22 +281,22 @@ func (r *InfisicalSecretReconciler) UpdateInfisicalManagedKubeSecret(ctx context
|
||||
}
|
||||
|
||||
managedKubeSecret.Data = plainProcessedSecrets
|
||||
managedKubeSecret.ObjectMeta.Annotations[SECRET_VERSION_ANNOTATION] = ETag
|
||||
managedKubeSecret.ObjectMeta.Annotations[constants.SECRET_VERSION_ANNOTATION] = ETag
|
||||
|
||||
err := r.Client.Update(ctx, &managedKubeSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to update Kubernetes secret because [%w]", err)
|
||||
}
|
||||
|
||||
fmt.Println("successfully updated managed Kubernetes secret")
|
||||
logger.Info("successfully updated managed Kubernetes secret")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) GetResourceVariables(infisicalSecret v1alpha1.InfisicalSecret) ResourceVariables {
|
||||
func (r *InfisicalSecretReconciler) getResourceVariables(infisicalSecret v1alpha1.InfisicalSecret) util.ResourceVariables {
|
||||
|
||||
var resourceVariables ResourceVariables
|
||||
var resourceVariables util.ResourceVariables
|
||||
|
||||
if _, ok := resourceVariablesMap[string(infisicalSecret.UID)]; !ok {
|
||||
if _, ok := infisicalSecretResourceVariablesMap[string(infisicalSecret.UID)]; !ok {
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
@ -379,52 +306,52 @@ func (r *InfisicalSecretReconciler) GetResourceVariables(infisicalSecret v1alpha
|
||||
UserAgent: api.USER_AGENT_NAME,
|
||||
})
|
||||
|
||||
resourceVariablesMap[string(infisicalSecret.UID)] = ResourceVariables{
|
||||
infisicalClient: client,
|
||||
cancelCtx: cancel,
|
||||
authDetails: AuthenticationDetails{},
|
||||
infisicalSecretResourceVariablesMap[string(infisicalSecret.UID)] = util.ResourceVariables{
|
||||
InfisicalClient: client,
|
||||
CancelCtx: cancel,
|
||||
AuthDetails: util.AuthenticationDetails{},
|
||||
}
|
||||
|
||||
resourceVariables = resourceVariablesMap[string(infisicalSecret.UID)]
|
||||
resourceVariables = infisicalSecretResourceVariablesMap[string(infisicalSecret.UID)]
|
||||
|
||||
} else {
|
||||
resourceVariables = resourceVariablesMap[string(infisicalSecret.UID)]
|
||||
resourceVariables = infisicalSecretResourceVariablesMap[string(infisicalSecret.UID)]
|
||||
}
|
||||
|
||||
return resourceVariables
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) UpdateResourceVariables(infisicalSecret v1alpha1.InfisicalSecret, resourceVariables ResourceVariables) {
|
||||
resourceVariablesMap[string(infisicalSecret.UID)] = resourceVariables
|
||||
func (r *InfisicalSecretReconciler) updateResourceVariables(infisicalSecret v1alpha1.InfisicalSecret, resourceVariables util.ResourceVariables) {
|
||||
infisicalSecretResourceVariablesMap[string(infisicalSecret.UID)] = resourceVariables
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret) error {
|
||||
func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context, logger logr.Logger, infisicalSecret v1alpha1.InfisicalSecret) error {
|
||||
|
||||
resourceVariables := r.GetResourceVariables(infisicalSecret)
|
||||
infisicalClient := resourceVariables.infisicalClient
|
||||
cancelCtx := resourceVariables.cancelCtx
|
||||
authDetails := resourceVariables.authDetails
|
||||
resourceVariables := r.getResourceVariables(infisicalSecret)
|
||||
infisicalClient := resourceVariables.InfisicalClient
|
||||
cancelCtx := resourceVariables.CancelCtx
|
||||
authDetails := resourceVariables.AuthDetails
|
||||
var err error
|
||||
|
||||
if authDetails.authStrategy == "" {
|
||||
fmt.Println("ReconcileInfisicalSecret: No authentication strategy found. Attempting to authenticate")
|
||||
authDetails, err = r.HandleAuthentication(ctx, infisicalSecret, infisicalClient)
|
||||
r.SetInfisicalTokenLoadCondition(ctx, &infisicalSecret, authDetails.authStrategy, err)
|
||||
if authDetails.AuthStrategy == "" {
|
||||
logger.Info("No authentication strategy found. Attempting to authenticate")
|
||||
authDetails, err = r.handleAuthentication(ctx, infisicalSecret, infisicalClient)
|
||||
r.SetInfisicalTokenLoadCondition(ctx, logger, &infisicalSecret, authDetails.AuthStrategy, err)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to authenticate [err=%s]", err)
|
||||
}
|
||||
|
||||
r.UpdateResourceVariables(infisicalSecret, ResourceVariables{
|
||||
infisicalClient: infisicalClient,
|
||||
cancelCtx: cancelCtx,
|
||||
authDetails: authDetails,
|
||||
r.updateResourceVariables(infisicalSecret, util.ResourceVariables{
|
||||
InfisicalClient: infisicalClient,
|
||||
CancelCtx: cancelCtx,
|
||||
AuthDetails: authDetails,
|
||||
})
|
||||
}
|
||||
|
||||
// Look for managed secret by name and namespace
|
||||
managedKubeSecret, err := r.GetKubeSecretByNamespacedName(ctx, types.NamespacedName{
|
||||
managedKubeSecret, err := util.GetKubeSecretByNamespacedName(ctx, r.Client, types.NamespacedName{
|
||||
Name: infisicalSecret.Spec.ManagedSecretReference.SecretName,
|
||||
Namespace: infisicalSecret.Spec.ManagedSecretReference.SecretNamespace,
|
||||
})
|
||||
@ -436,14 +363,14 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
|
||||
// Get exiting Etag if exists
|
||||
secretVersionBasedOnETag := ""
|
||||
if managedKubeSecret != nil {
|
||||
secretVersionBasedOnETag = managedKubeSecret.Annotations[SECRET_VERSION_ANNOTATION]
|
||||
secretVersionBasedOnETag = managedKubeSecret.Annotations[constants.SECRET_VERSION_ANNOTATION]
|
||||
}
|
||||
|
||||
var plainTextSecretsFromApi []model.SingleEnvironmentVariable
|
||||
var updateDetails model.RequestUpdateUpdateDetails
|
||||
|
||||
if authDetails.authStrategy == AuthStrategy.SERVICE_ACCOUNT { // Service Account // ! Legacy auth method
|
||||
serviceAccountCreds, err := r.GetInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
|
||||
if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_ACCOUNT { // Service Account // ! Legacy auth method
|
||||
serviceAccountCreds, err := r.getInfisicalServiceAccountCredentialsFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service account creds from kube secret [err=%s]", err)
|
||||
}
|
||||
@ -453,10 +380,10 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
|
||||
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
|
||||
}
|
||||
|
||||
fmt.Println("ReconcileInfisicalSecret: Fetched secrets via service account")
|
||||
logger.Info("ReconcileInfisicalSecret: Fetched secrets via service account")
|
||||
|
||||
} else if authDetails.authStrategy == AuthStrategy.SERVICE_TOKEN { // Service Tokens // ! Legacy / Deprecated auth method
|
||||
infisicalToken, err := r.GetInfisicalTokenFromKubeSecret(ctx, infisicalSecret)
|
||||
} else if authDetails.AuthStrategy == util.AuthStrategy.SERVICE_TOKEN { // Service Tokens // ! Legacy / Deprecated auth method
|
||||
infisicalToken, err := r.getInfisicalTokenFromKubeSecret(ctx, infisicalSecret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("ReconcileInfisicalSecret: unable to get service token from kube secret [err=%s]", err)
|
||||
}
|
||||
@ -470,28 +397,30 @@ func (r *InfisicalSecretReconciler) ReconcileInfisicalSecret(ctx context.Context
|
||||
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
|
||||
}
|
||||
|
||||
fmt.Println("ReconcileInfisicalSecret: Fetched secrets via [type=SERVICE_TOKEN]")
|
||||
} else if authDetails.isMachineIdentityAuth { // * Machine Identity authentication, the SDK will be authenticated at this point
|
||||
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaMachineIdentity(infisicalClient, secretVersionBasedOnETag, authDetails.machineIdentityScope)
|
||||
logger.Info("ReconcileInfisicalSecret: Fetched secrets via [type=SERVICE_TOKEN]")
|
||||
|
||||
} else if authDetails.IsMachineIdentityAuth { // * Machine Identity authentication, the SDK will be authenticated at this point
|
||||
plainTextSecretsFromApi, updateDetails, err = util.GetPlainTextSecretsViaMachineIdentity(infisicalClient, secretVersionBasedOnETag, authDetails.MachineIdentityScope)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("\nfailed to get secrets because [err=%v]", err)
|
||||
}
|
||||
fmt.Printf("ReconcileInfisicalSecret: Fetched secrets via machine identity [type=%v]\n", authDetails.authStrategy)
|
||||
|
||||
logger.Info(fmt.Sprintf("ReconcileInfisicalSecret: Fetched secrets via machine identity [type=%v]", authDetails.AuthStrategy))
|
||||
|
||||
} else {
|
||||
return errors.New("no authentication method provided yet. Please configure a authentication method then try again")
|
||||
}
|
||||
|
||||
if !updateDetails.Modified {
|
||||
fmt.Println("No secrets modified so reconcile not needed")
|
||||
logger.Info("ReconcileInfisicalSecret: No secrets modified so reconcile not needed")
|
||||
return nil
|
||||
}
|
||||
|
||||
if managedKubeSecret == nil {
|
||||
return r.CreateInfisicalManagedKubeSecret(ctx, infisicalSecret, plainTextSecretsFromApi, updateDetails.ETag)
|
||||
return r.createInfisicalManagedKubeSecret(ctx, logger, infisicalSecret, plainTextSecretsFromApi, updateDetails.ETag)
|
||||
} else {
|
||||
return r.UpdateInfisicalManagedKubeSecret(ctx, infisicalSecret, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag)
|
||||
return r.updateInfisicalManagedKubeSecret(ctx, logger, infisicalSecret, *managedKubeSecret, plainTextSecretsFromApi, updateDetails.ETag)
|
||||
}
|
||||
|
||||
}
|
@ -1,150 +0,0 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/util"
|
||||
infisicalSdk "github.com/infisical/go-sdk"
|
||||
)
|
||||
|
||||
type AuthStrategyType string
|
||||
|
||||
var AuthStrategy = struct {
|
||||
SERVICE_TOKEN AuthStrategyType
|
||||
SERVICE_ACCOUNT AuthStrategyType
|
||||
UNIVERSAL_MACHINE_IDENTITY AuthStrategyType
|
||||
KUBERNETES_MACHINE_IDENTITY AuthStrategyType
|
||||
AWS_IAM_MACHINE_IDENTITY AuthStrategyType
|
||||
AZURE_MACHINE_IDENTITY AuthStrategyType
|
||||
GCP_ID_TOKEN_MACHINE_IDENTITY AuthStrategyType
|
||||
GCP_IAM_MACHINE_IDENTITY AuthStrategyType
|
||||
}{
|
||||
SERVICE_TOKEN: "SERVICE_TOKEN",
|
||||
SERVICE_ACCOUNT: "SERVICE_ACCOUNT",
|
||||
UNIVERSAL_MACHINE_IDENTITY: "UNIVERSAL_MACHINE_IDENTITY",
|
||||
KUBERNETES_MACHINE_IDENTITY: "KUBERNETES_AUTH_MACHINE_IDENTITY",
|
||||
AWS_IAM_MACHINE_IDENTITY: "AWS_IAM_MACHINE_IDENTITY",
|
||||
AZURE_MACHINE_IDENTITY: "AZURE_MACHINE_IDENTITY",
|
||||
GCP_ID_TOKEN_MACHINE_IDENTITY: "GCP_ID_TOKEN_MACHINE_IDENTITY",
|
||||
GCP_IAM_MACHINE_IDENTITY: "GCP_IAM_MACHINE_IDENTITY",
|
||||
}
|
||||
|
||||
type AuthenticationDetails struct {
|
||||
authStrategy AuthStrategyType
|
||||
machineIdentityScope v1alpha1.MachineIdentityScopeInWorkspace // This will only be set if a machine identity auth method is used (e.g. UniversalAuth or KubernetesAuth, etc.)
|
||||
isMachineIdentityAuth bool
|
||||
}
|
||||
|
||||
var ErrAuthNotApplicable = errors.New("authentication not applicable")
|
||||
|
||||
func (r *InfisicalSecretReconciler) handleUniversalAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
|
||||
// Machine Identities:
|
||||
universalAuthKubeSecret, err := r.GetInfisicalUniversalAuthFromKubeSecret(ctx, infisicalSecret)
|
||||
universalAuthSpec := infisicalSecret.Spec.Authentication.UniversalAuth
|
||||
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get machine identity creds from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
if universalAuthKubeSecret.ClientId == "" && universalAuthKubeSecret.ClientSecret == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err = infisicalClient.Auth().UniversalAuthLogin(universalAuthKubeSecret.ClientId, universalAuthKubeSecret.ClientSecret)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with machine identity credentials [err=%s]", err)
|
||||
}
|
||||
|
||||
fmt.Println("Successfully authenticated with machine identity credentials")
|
||||
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.UNIVERSAL_MACHINE_IDENTITY, machineIdentityScope: universalAuthSpec.SecretsScope, isMachineIdentityAuth: true}, nil
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) handleKubernetesAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
kubernetesAuthSpec := infisicalSecret.Spec.Authentication.KubernetesAuth
|
||||
|
||||
if kubernetesAuthSpec.IdentityID == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
serviceAccountToken, err := util.GetServiceAccountToken(r.Client, kubernetesAuthSpec.ServiceAccountRef.Namespace, kubernetesAuthSpec.ServiceAccountRef.Name)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to get service account token [err=%s]", err)
|
||||
}
|
||||
|
||||
_, err = infisicalClient.Auth().KubernetesRawServiceAccountTokenLogin(kubernetesAuthSpec.IdentityID, serviceAccountToken)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with Kubernetes native auth [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.KUBERNETES_MACHINE_IDENTITY, machineIdentityScope: kubernetesAuthSpec.SecretsScope, isMachineIdentityAuth: true}, nil
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) handleAwsIamAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
awsIamAuthSpec := infisicalSecret.Spec.Authentication.AwsIamAuth
|
||||
|
||||
if awsIamAuthSpec.IdentityID == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err := infisicalClient.Auth().AwsIamAuthLogin(awsIamAuthSpec.IdentityID)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with AWS IAM auth [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.AWS_IAM_MACHINE_IDENTITY, machineIdentityScope: awsIamAuthSpec.SecretsScope, isMachineIdentityAuth: true}, nil
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) handleAzureAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
azureAuthSpec := infisicalSecret.Spec.Authentication.AzureAuth
|
||||
|
||||
if azureAuthSpec.IdentityID == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err := infisicalClient.Auth().AzureAuthLogin(azureAuthSpec.IdentityID, azureAuthSpec.Resource) // If resource is empty(""), it will default to "https://management.azure.com/" in the SDK.
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with Azure auth [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.AZURE_MACHINE_IDENTITY, machineIdentityScope: azureAuthSpec.SecretsScope, isMachineIdentityAuth: true}, nil
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) handleGcpIdTokenAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
gcpIdTokenSpec := infisicalSecret.Spec.Authentication.GcpIdTokenAuth
|
||||
|
||||
if gcpIdTokenSpec.IdentityID == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err := infisicalClient.Auth().GcpIdTokenAuthLogin(gcpIdTokenSpec.IdentityID)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with GCP Id Token auth [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.GCP_ID_TOKEN_MACHINE_IDENTITY, machineIdentityScope: gcpIdTokenSpec.SecretsScope, isMachineIdentityAuth: true}, nil
|
||||
|
||||
}
|
||||
|
||||
func (r *InfisicalSecretReconciler) handleGcpIamAuth(ctx context.Context, infisicalSecret v1alpha1.InfisicalSecret, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
gcpIamSpec := infisicalSecret.Spec.Authentication.GcpIamAuth
|
||||
|
||||
if gcpIamSpec.IdentityID == "" && gcpIamSpec.ServiceAccountKeyFilePath == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err := infisicalClient.Auth().GcpIamAuthLogin(gcpIamSpec.IdentityID, gcpIamSpec.ServiceAccountKeyFilePath)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with GCP IAM auth [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{authStrategy: AuthStrategy.GCP_IAM_MACHINE_IDENTITY, machineIdentityScope: gcpIamSpec.SecretsScope, isMachineIdentityAuth: true}, nil
|
||||
}
|
@ -13,6 +13,215 @@ metadata:
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
creationTimestamp: null
|
||||
name: infisicalpushsecrets.secrets.infisical.com
|
||||
spec:
|
||||
group: secrets.infisical.com
|
||||
names:
|
||||
kind: InfisicalPushSecret
|
||||
listKind: InfisicalPushSecretList
|
||||
plural: infisicalpushsecrets
|
||||
singular: infisicalpushsecret
|
||||
scope: Namespaced
|
||||
versions:
|
||||
- name: v1alpha1
|
||||
schema:
|
||||
openAPIV3Schema:
|
||||
description: InfisicalPushSecret is the Schema for the infisicalpushsecrets API
|
||||
properties:
|
||||
apiVersion:
|
||||
description: 'APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
|
||||
type: string
|
||||
kind:
|
||||
description: 'Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
spec:
|
||||
description: InfisicalPushSecretSpec defines the desired state of InfisicalPushSecret
|
||||
properties:
|
||||
authentication:
|
||||
properties:
|
||||
awsIamAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
type: object
|
||||
azureAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
resource:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
type: object
|
||||
gcpIamAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
serviceAccountKeyFilePath:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
- serviceAccountKeyFilePath
|
||||
type: object
|
||||
gcpIdTokenAuth:
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
required:
|
||||
- identityId
|
||||
type: object
|
||||
kubernetesAuth:
|
||||
description: Rest of your types should be defined similarly...
|
||||
properties:
|
||||
identityId:
|
||||
type: string
|
||||
serviceAccountRef:
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
namespace:
|
||||
type: string
|
||||
required:
|
||||
- name
|
||||
- namespace
|
||||
type: object
|
||||
required:
|
||||
- identityId
|
||||
- serviceAccountRef
|
||||
type: object
|
||||
universalAuth:
|
||||
description: PushSecretUniversalAuth defines universal authentication
|
||||
properties:
|
||||
credentialsRef:
|
||||
properties:
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The name space where the Kubernetes Secret is located
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
required:
|
||||
- credentialsRef
|
||||
type: object
|
||||
type: object
|
||||
deletionPolicy:
|
||||
type: string
|
||||
destination:
|
||||
properties:
|
||||
envSlug:
|
||||
type: string
|
||||
projectId:
|
||||
type: string
|
||||
secretsPath:
|
||||
type: string
|
||||
required:
|
||||
- envSlug
|
||||
- projectId
|
||||
- secretsPath
|
||||
type: object
|
||||
hostAPI:
|
||||
type: string
|
||||
push:
|
||||
properties:
|
||||
secret:
|
||||
properties:
|
||||
secretName:
|
||||
description: The name of the Kubernetes Secret
|
||||
type: string
|
||||
secretNamespace:
|
||||
description: The name space where the Kubernetes Secret is located
|
||||
type: string
|
||||
required:
|
||||
- secretName
|
||||
- secretNamespace
|
||||
type: object
|
||||
required:
|
||||
- secret
|
||||
type: object
|
||||
resyncInterval:
|
||||
type: string
|
||||
updatePolicy:
|
||||
type: string
|
||||
required:
|
||||
- destination
|
||||
- push
|
||||
- resyncInterval
|
||||
type: object
|
||||
status:
|
||||
description: InfisicalPushSecretStatus defines the observed state of InfisicalPushSecret
|
||||
properties:
|
||||
conditions:
|
||||
items:
|
||||
description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, \n type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }"
|
||||
properties:
|
||||
lastTransitionTime:
|
||||
description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
|
||||
format: date-time
|
||||
type: string
|
||||
message:
|
||||
description: message is a human readable message indicating details about the transition. This may be an empty string.
|
||||
maxLength: 32768
|
||||
type: string
|
||||
observedGeneration:
|
||||
description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance.
|
||||
format: int64
|
||||
minimum: 0
|
||||
type: integer
|
||||
reason:
|
||||
description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty.
|
||||
maxLength: 1024
|
||||
minLength: 1
|
||||
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
|
||||
type: string
|
||||
status:
|
||||
description: status of the condition, one of True, False, Unknown.
|
||||
enum:
|
||||
- "True"
|
||||
- "False"
|
||||
- Unknown
|
||||
type: string
|
||||
type:
|
||||
description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt)
|
||||
maxLength: 316
|
||||
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
|
||||
type: string
|
||||
required:
|
||||
- lastTransitionTime
|
||||
- message
|
||||
- reason
|
||||
- status
|
||||
- type
|
||||
type: object
|
||||
type: array
|
||||
managedSecrets:
|
||||
additionalProperties:
|
||||
type: string
|
||||
description: managed secrets is a map where the key is the ID, and the value is the secret key (string[id], string[key] )
|
||||
type: object
|
||||
required:
|
||||
- conditions
|
||||
- managedSecrets
|
||||
type: object
|
||||
type: object
|
||||
served: true
|
||||
storage: true
|
||||
subresources:
|
||||
status: {}
|
||||
---
|
||||
apiVersion: apiextensions.k8s.io/v1
|
||||
kind: CustomResourceDefinition
|
||||
metadata:
|
||||
annotations:
|
||||
controller-gen.kubebuilder.io/version: v0.10.0
|
||||
|
@ -16,7 +16,8 @@ import (
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
secretsv1alpha1 "github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"github.com/Infisical/infisical/k8-operator/controllers"
|
||||
infisicalPushSecretController "github.com/Infisical/infisical/k8-operator/controllers/infisicalpushsecret"
|
||||
infisicalSecretController "github.com/Infisical/infisical/k8-operator/controllers/infisicalsecret"
|
||||
//+kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
@ -81,13 +82,24 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err = (&controllers.InfisicalSecretReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
if err = (&infisicalSecretController.InfisicalSecretReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
BaseLogger: ctrl.Log,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "InfisicalSecret")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err = (&infisicalPushSecretController.InfisicalPushSecretReconciler{
|
||||
Client: mgr.GetClient(),
|
||||
Scheme: mgr.GetScheme(),
|
||||
BaseLogger: ctrl.Log,
|
||||
}).SetupWithManager(mgr); err != nil {
|
||||
setupLog.Error(err, "unable to create controller", "controller", "InfisicalPushSecret")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
//+kubebuilder:scaffold:builder
|
||||
|
||||
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
|
||||
|
@ -24,10 +24,6 @@ func CallGetServiceTokenDetailsV2(httpClient *resty.Client) (GetServiceTokenDeta
|
||||
return GetServiceTokenDetailsResponse{}, fmt.Errorf("CallGetServiceTokenDetails: Unsuccessful response: [response=%s]", response)
|
||||
}
|
||||
|
||||
// logging for better debugging and user experience
|
||||
fmt.Printf("Workspace ID: %v\n", tokenDetailsResponse.Workspace)
|
||||
fmt.Printf("TokenName: %v\n", tokenDetailsResponse.Name)
|
||||
|
||||
return tokenDetailsResponse, nil
|
||||
}
|
||||
|
||||
|
24
k8-operator/packages/constants/constants.go
Normal file
24
k8-operator/packages/constants/constants.go
Normal file
@ -0,0 +1,24 @@
|
||||
package constants
|
||||
|
||||
const SERVICE_ACCOUNT_ACCESS_KEY = "serviceAccountAccessKey"
|
||||
const SERVICE_ACCOUNT_PUBLIC_KEY = "serviceAccountPublicKey"
|
||||
const SERVICE_ACCOUNT_PRIVATE_KEY = "serviceAccountPrivateKey"
|
||||
|
||||
const INFISICAL_MACHINE_IDENTITY_CLIENT_ID = "clientId"
|
||||
const INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET = "clientSecret"
|
||||
|
||||
const INFISICAL_TOKEN_SECRET_KEY_NAME = "infisicalToken"
|
||||
const SECRET_VERSION_ANNOTATION = "secrets.infisical.com/version" // used to set the version of secrets via Etag
|
||||
const OPERATOR_SETTINGS_CONFIGMAP_NAME = "infisical-config"
|
||||
const OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE = "infisical-operator-system"
|
||||
const INFISICAL_DOMAIN = "https://app.infisical.com/api"
|
||||
|
||||
const INFISICAL_PUSH_SECRET_FINALIZER_NAME = "pushsecret.secrets.infisical.com/finalizer"
|
||||
|
||||
type PushSecretReplacePolicy string
|
||||
type PushSecretDeletionPolicy string
|
||||
|
||||
const (
|
||||
PUSH_SECRET_REPLACE_POLICY_ENABLED PushSecretReplacePolicy = "Replace"
|
||||
PUSH_SECRET_DELETE_POLICY_ENABLED PushSecretDeletionPolicy = "Delete"
|
||||
)
|
45
k8-operator/packages/controllerutil/util.go
Normal file
45
k8-operator/packages/controllerutil/util.go
Normal file
@ -0,0 +1,45 @@
|
||||
package controllerhelpers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Infisical/infisical/k8-operator/packages/constants"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
k8Errors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func GetInfisicalConfigMap(ctx context.Context, client client.Client) (configMap map[string]string, errToReturn error) {
|
||||
// default key values
|
||||
defaultConfigMapData := make(map[string]string)
|
||||
defaultConfigMapData["hostAPI"] = constants.INFISICAL_DOMAIN
|
||||
|
||||
kubeConfigMap := &corev1.ConfigMap{}
|
||||
err := client.Get(ctx, types.NamespacedName{
|
||||
Namespace: constants.OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE,
|
||||
Name: constants.OPERATOR_SETTINGS_CONFIGMAP_NAME,
|
||||
}, kubeConfigMap)
|
||||
|
||||
if err != nil {
|
||||
if k8Errors.IsNotFound(err) {
|
||||
kubeConfigMap = nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("GetConfigMapByNamespacedName: unable to fetch config map in [namespacedName=%s] [err=%s]", constants.OPERATOR_SETTINGS_CONFIGMAP_NAMESPACE, err)
|
||||
}
|
||||
}
|
||||
|
||||
if kubeConfigMap == nil {
|
||||
return defaultConfigMapData, nil
|
||||
} else {
|
||||
for key, value := range defaultConfigMapData {
|
||||
_, exists := kubeConfigMap.Data[key]
|
||||
if !exists {
|
||||
kubeConfigMap.Data[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return kubeConfigMap.Data, nil
|
||||
}
|
||||
}
|
@ -4,7 +4,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"errors"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
infisicalSdk "github.com/infisical/go-sdk"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
@ -32,3 +37,324 @@ func GetServiceAccountToken(k8sClient client.Client, namespace string, serviceAc
|
||||
|
||||
return string(token), nil
|
||||
}
|
||||
|
||||
type AuthStrategyType string
|
||||
|
||||
var AuthStrategy = struct {
|
||||
SERVICE_TOKEN AuthStrategyType
|
||||
SERVICE_ACCOUNT AuthStrategyType
|
||||
UNIVERSAL_MACHINE_IDENTITY AuthStrategyType
|
||||
KUBERNETES_MACHINE_IDENTITY AuthStrategyType
|
||||
AWS_IAM_MACHINE_IDENTITY AuthStrategyType
|
||||
AZURE_MACHINE_IDENTITY AuthStrategyType
|
||||
GCP_ID_TOKEN_MACHINE_IDENTITY AuthStrategyType
|
||||
GCP_IAM_MACHINE_IDENTITY AuthStrategyType
|
||||
}{
|
||||
SERVICE_TOKEN: "SERVICE_TOKEN",
|
||||
SERVICE_ACCOUNT: "SERVICE_ACCOUNT",
|
||||
UNIVERSAL_MACHINE_IDENTITY: "UNIVERSAL_MACHINE_IDENTITY",
|
||||
KUBERNETES_MACHINE_IDENTITY: "KUBERNETES_AUTH_MACHINE_IDENTITY",
|
||||
AWS_IAM_MACHINE_IDENTITY: "AWS_IAM_MACHINE_IDENTITY",
|
||||
AZURE_MACHINE_IDENTITY: "AZURE_MACHINE_IDENTITY",
|
||||
GCP_ID_TOKEN_MACHINE_IDENTITY: "GCP_ID_TOKEN_MACHINE_IDENTITY",
|
||||
GCP_IAM_MACHINE_IDENTITY: "GCP_IAM_MACHINE_IDENTITY",
|
||||
}
|
||||
|
||||
type SecretCrdType string
|
||||
|
||||
var SecretCrd = struct {
|
||||
INFISICAL_SECRET SecretCrdType
|
||||
INFISICAL_PUSH_SECRET SecretCrdType
|
||||
}{
|
||||
INFISICAL_SECRET: "INFISICAL_SECRET",
|
||||
INFISICAL_PUSH_SECRET: "INFISICAL_PUSH_SECRET",
|
||||
}
|
||||
|
||||
type SecretAuthInput struct {
|
||||
Secret interface{}
|
||||
Type SecretCrdType
|
||||
}
|
||||
|
||||
type AuthenticationDetails struct {
|
||||
AuthStrategy AuthStrategyType
|
||||
MachineIdentityScope v1alpha1.MachineIdentityScopeInWorkspace // This will only be set if a machine identity auth method is used (e.g. UniversalAuth or KubernetesAuth, etc.)
|
||||
IsMachineIdentityAuth bool
|
||||
SecretType SecretCrdType
|
||||
}
|
||||
|
||||
var ErrAuthNotApplicable = errors.New("authentication not applicable")
|
||||
|
||||
func HandleUniversalAuth(ctx context.Context, reconcilerClient client.Client, secretCrd SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
|
||||
var universalAuthSpec v1alpha1.UniversalAuthDetails
|
||||
|
||||
switch secretCrd.Type {
|
||||
case SecretCrd.INFISICAL_SECRET:
|
||||
infisicalSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalSecret)
|
||||
|
||||
if !ok {
|
||||
return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalSecret")
|
||||
}
|
||||
universalAuthSpec = infisicalSecret.Spec.Authentication.UniversalAuth
|
||||
case SecretCrd.INFISICAL_PUSH_SECRET:
|
||||
infisicalPushSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalPushSecret)
|
||||
|
||||
if !ok {
|
||||
return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalPushSecret")
|
||||
}
|
||||
|
||||
universalAuthSpec = v1alpha1.UniversalAuthDetails{
|
||||
CredentialsRef: infisicalPushSecret.Spec.Authentication.UniversalAuth.CredentialsRef,
|
||||
SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{},
|
||||
}
|
||||
}
|
||||
|
||||
universalAuthKubeSecret, err := GetInfisicalUniversalAuthFromKubeSecret(ctx, reconcilerClient, v1alpha1.KubeSecretReference{
|
||||
SecretNamespace: universalAuthSpec.CredentialsRef.SecretNamespace,
|
||||
SecretName: universalAuthSpec.CredentialsRef.SecretName,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("ReconcileInfisicalSecret: unable to get machine identity creds from kube secret [err=%s]", err)
|
||||
}
|
||||
|
||||
if universalAuthKubeSecret.ClientId == "" && universalAuthKubeSecret.ClientSecret == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err = infisicalClient.Auth().UniversalAuthLogin(universalAuthKubeSecret.ClientId, universalAuthKubeSecret.ClientSecret)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with machine identity credentials [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{
|
||||
AuthStrategy: AuthStrategy.UNIVERSAL_MACHINE_IDENTITY,
|
||||
MachineIdentityScope: universalAuthSpec.SecretsScope,
|
||||
IsMachineIdentityAuth: true,
|
||||
SecretType: secretCrd.Type,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func HandleKubernetesAuth(ctx context.Context, reconcilerClient client.Client, secretCrd SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
var kubernetesAuthSpec v1alpha1.KubernetesAuthDetails
|
||||
|
||||
switch secretCrd.Type {
|
||||
case SecretCrd.INFISICAL_SECRET:
|
||||
infisicalSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalSecret)
|
||||
|
||||
if !ok {
|
||||
return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalSecret")
|
||||
}
|
||||
kubernetesAuthSpec = infisicalSecret.Spec.Authentication.KubernetesAuth
|
||||
case SecretCrd.INFISICAL_PUSH_SECRET:
|
||||
infisicalPushSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalPushSecret)
|
||||
|
||||
if !ok {
|
||||
return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalPushSecret")
|
||||
}
|
||||
kubernetesAuthSpec = v1alpha1.KubernetesAuthDetails{
|
||||
IdentityID: infisicalPushSecret.Spec.Authentication.KubernetesAuth.IdentityID,
|
||||
ServiceAccountRef: v1alpha1.KubernetesServiceAccountRef{
|
||||
Namespace: infisicalPushSecret.Spec.Authentication.KubernetesAuth.ServiceAccountRef.Namespace,
|
||||
Name: infisicalPushSecret.Spec.Authentication.KubernetesAuth.ServiceAccountRef.Name,
|
||||
},
|
||||
SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{},
|
||||
}
|
||||
}
|
||||
|
||||
if kubernetesAuthSpec.IdentityID == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
serviceAccountToken, err := GetServiceAccountToken(reconcilerClient, kubernetesAuthSpec.ServiceAccountRef.Namespace, kubernetesAuthSpec.ServiceAccountRef.Name)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to get service account token [err=%s]", err)
|
||||
}
|
||||
|
||||
_, err = infisicalClient.Auth().KubernetesRawServiceAccountTokenLogin(kubernetesAuthSpec.IdentityID, serviceAccountToken)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with Kubernetes native auth [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{
|
||||
AuthStrategy: AuthStrategy.KUBERNETES_MACHINE_IDENTITY,
|
||||
MachineIdentityScope: kubernetesAuthSpec.SecretsScope,
|
||||
IsMachineIdentityAuth: true,
|
||||
SecretType: secretCrd.Type,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func HandleAwsIamAuth(ctx context.Context, reconcilerClient client.Client, secretCrd SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
awsIamAuthSpec := v1alpha1.AWSIamAuthDetails{}
|
||||
|
||||
switch secretCrd.Type {
|
||||
case SecretCrd.INFISICAL_SECRET:
|
||||
infisicalSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalSecret)
|
||||
|
||||
if !ok {
|
||||
return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalSecret")
|
||||
}
|
||||
|
||||
awsIamAuthSpec = infisicalSecret.Spec.Authentication.AwsIamAuth
|
||||
case SecretCrd.INFISICAL_PUSH_SECRET:
|
||||
infisicalPushSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalPushSecret)
|
||||
|
||||
if !ok {
|
||||
return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalPushSecret")
|
||||
}
|
||||
|
||||
awsIamAuthSpec = v1alpha1.AWSIamAuthDetails{
|
||||
IdentityID: infisicalPushSecret.Spec.Authentication.AwsIamAuth.IdentityID,
|
||||
SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{},
|
||||
}
|
||||
}
|
||||
|
||||
if awsIamAuthSpec.IdentityID == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err := infisicalClient.Auth().AwsIamAuthLogin(awsIamAuthSpec.IdentityID)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with AWS IAM auth [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{
|
||||
AuthStrategy: AuthStrategy.AWS_IAM_MACHINE_IDENTITY,
|
||||
MachineIdentityScope: awsIamAuthSpec.SecretsScope,
|
||||
IsMachineIdentityAuth: true,
|
||||
SecretType: secretCrd.Type,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func HandleAzureAuth(ctx context.Context, reconcilerClient client.Client, secretCrd SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
azureAuthSpec := v1alpha1.AzureAuthDetails{}
|
||||
|
||||
switch secretCrd.Type {
|
||||
case SecretCrd.INFISICAL_SECRET:
|
||||
infisicalSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalSecret)
|
||||
|
||||
if !ok {
|
||||
return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalSecret")
|
||||
}
|
||||
|
||||
azureAuthSpec = infisicalSecret.Spec.Authentication.AzureAuth
|
||||
|
||||
case SecretCrd.INFISICAL_PUSH_SECRET:
|
||||
infisicalPushSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalPushSecret)
|
||||
|
||||
if !ok {
|
||||
return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalPushSecret")
|
||||
}
|
||||
|
||||
azureAuthSpec = v1alpha1.AzureAuthDetails{
|
||||
IdentityID: infisicalPushSecret.Spec.Authentication.AzureAuth.IdentityID,
|
||||
Resource: infisicalPushSecret.Spec.Authentication.AzureAuth.Resource,
|
||||
SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{},
|
||||
}
|
||||
}
|
||||
|
||||
if azureAuthSpec.IdentityID == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err := infisicalClient.Auth().AzureAuthLogin(azureAuthSpec.IdentityID, azureAuthSpec.Resource) // If resource is empty(""), it will default to "https://management.azure.com/" in the SDK.
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with Azure auth [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{
|
||||
AuthStrategy: AuthStrategy.AZURE_MACHINE_IDENTITY,
|
||||
MachineIdentityScope: azureAuthSpec.SecretsScope,
|
||||
IsMachineIdentityAuth: true,
|
||||
SecretType: secretCrd.Type,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func HandleGcpIdTokenAuth(ctx context.Context, reconcilerClient client.Client, secretCrd SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
gcpIdTokenSpec := v1alpha1.GCPIdTokenAuthDetails{}
|
||||
|
||||
switch secretCrd.Type {
|
||||
case SecretCrd.INFISICAL_SECRET:
|
||||
infisicalSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalSecret)
|
||||
|
||||
if !ok {
|
||||
return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalSecret")
|
||||
}
|
||||
|
||||
gcpIdTokenSpec = infisicalSecret.Spec.Authentication.GcpIdTokenAuth
|
||||
case SecretCrd.INFISICAL_PUSH_SECRET:
|
||||
infisicalPushSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalPushSecret)
|
||||
|
||||
if !ok {
|
||||
return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalPushSecret")
|
||||
}
|
||||
|
||||
gcpIdTokenSpec = v1alpha1.GCPIdTokenAuthDetails{
|
||||
IdentityID: infisicalPushSecret.Spec.Authentication.GcpIdTokenAuth.IdentityID,
|
||||
SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{},
|
||||
}
|
||||
}
|
||||
|
||||
if gcpIdTokenSpec.IdentityID == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err := infisicalClient.Auth().GcpIdTokenAuthLogin(gcpIdTokenSpec.IdentityID)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with GCP Id Token auth [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{
|
||||
AuthStrategy: AuthStrategy.GCP_ID_TOKEN_MACHINE_IDENTITY,
|
||||
MachineIdentityScope: gcpIdTokenSpec.SecretsScope,
|
||||
IsMachineIdentityAuth: true,
|
||||
SecretType: secretCrd.Type,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func HandleGcpIamAuth(ctx context.Context, reconcilerClient client.Client, secretCrd SecretAuthInput, infisicalClient infisicalSdk.InfisicalClientInterface) (AuthenticationDetails, error) {
|
||||
gcpIamSpec := v1alpha1.GcpIamAuthDetails{}
|
||||
|
||||
switch secretCrd.Type {
|
||||
case SecretCrd.INFISICAL_SECRET:
|
||||
infisicalSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalSecret)
|
||||
|
||||
if !ok {
|
||||
return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalSecret")
|
||||
}
|
||||
|
||||
gcpIamSpec = infisicalSecret.Spec.Authentication.GcpIamAuth
|
||||
case SecretCrd.INFISICAL_PUSH_SECRET:
|
||||
infisicalPushSecret, ok := secretCrd.Secret.(v1alpha1.InfisicalPushSecret)
|
||||
|
||||
if !ok {
|
||||
return AuthenticationDetails{}, errors.New("unable to cast secret to InfisicalPushSecret")
|
||||
}
|
||||
|
||||
gcpIamSpec = v1alpha1.GcpIamAuthDetails{
|
||||
IdentityID: infisicalPushSecret.Spec.Authentication.GcpIamAuth.IdentityID,
|
||||
ServiceAccountKeyFilePath: infisicalPushSecret.Spec.Authentication.GcpIamAuth.ServiceAccountKeyFilePath,
|
||||
SecretsScope: v1alpha1.MachineIdentityScopeInWorkspace{},
|
||||
}
|
||||
}
|
||||
|
||||
if gcpIamSpec.IdentityID == "" && gcpIamSpec.ServiceAccountKeyFilePath == "" {
|
||||
return AuthenticationDetails{}, ErrAuthNotApplicable
|
||||
}
|
||||
|
||||
_, err := infisicalClient.Auth().GcpIamAuthLogin(gcpIamSpec.IdentityID, gcpIamSpec.ServiceAccountKeyFilePath)
|
||||
if err != nil {
|
||||
return AuthenticationDetails{}, fmt.Errorf("unable to login with GCP IAM auth [err=%s]", err)
|
||||
}
|
||||
|
||||
return AuthenticationDetails{
|
||||
AuthStrategy: AuthStrategy.GCP_IAM_MACHINE_IDENTITY,
|
||||
MachineIdentityScope: gcpIamSpec.SecretsScope,
|
||||
IsMachineIdentityAuth: true,
|
||||
SecretType: secretCrd.Type,
|
||||
}, nil
|
||||
}
|
||||
|
50
k8-operator/packages/util/kubernetes.go
Normal file
50
k8-operator/packages/util/kubernetes.go
Normal file
@ -0,0 +1,50 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/Infisical/infisical/k8-operator/api/v1alpha1"
|
||||
"github.com/Infisical/infisical/k8-operator/packages/model"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
k8Errors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
const INFISICAL_MACHINE_IDENTITY_CLIENT_ID = "clientId"
|
||||
const INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET = "clientSecret"
|
||||
|
||||
func GetKubeSecretByNamespacedName(ctx context.Context, reconcilerClient client.Client, namespacedName types.NamespacedName) (*corev1.Secret, error) {
|
||||
kubeSecret := &corev1.Secret{}
|
||||
err := reconcilerClient.Get(ctx, namespacedName, kubeSecret)
|
||||
if err != nil {
|
||||
kubeSecret = nil
|
||||
}
|
||||
|
||||
return kubeSecret, err
|
||||
}
|
||||
|
||||
func GetInfisicalUniversalAuthFromKubeSecret(ctx context.Context, reconcilerClient client.Client, universalAuthRef v1alpha1.KubeSecretReference) (machineIdentityDetails model.MachineIdentityDetails, err error) {
|
||||
|
||||
universalAuthCredsFromKubeSecret, err := GetKubeSecretByNamespacedName(ctx, reconcilerClient, types.NamespacedName{
|
||||
Namespace: universalAuthRef.SecretNamespace,
|
||||
Name: universalAuthRef.SecretName,
|
||||
// Namespace: infisicalSecret.Spec.Authentication.UniversalAuth.CredentialsRef.SecretNamespace,
|
||||
// Name: infisicalSecret.Spec.Authentication.UniversalAuth.CredentialsRef.SecretName,
|
||||
})
|
||||
|
||||
if k8Errors.IsNotFound(err) {
|
||||
return model.MachineIdentityDetails{}, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return model.MachineIdentityDetails{}, fmt.Errorf("something went wrong when fetching your machine identity credentials [err=%s]", err)
|
||||
}
|
||||
|
||||
clientIdFromSecret := universalAuthCredsFromKubeSecret.Data[INFISICAL_MACHINE_IDENTITY_CLIENT_ID]
|
||||
clientSecretFromSecret := universalAuthCredsFromKubeSecret.Data[INFISICAL_MACHINE_IDENTITY_CLIENT_SECRET]
|
||||
|
||||
return model.MachineIdentityDetails{ClientId: string(clientIdFromSecret), ClientSecret: string(clientSecretFromSecret)}, nil
|
||||
|
||||
}
|
13
k8-operator/packages/util/models.go
Normal file
13
k8-operator/packages/util/models.go
Normal file
@ -0,0 +1,13 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
infisicalSdk "github.com/infisical/go-sdk"
|
||||
)
|
||||
|
||||
type ResourceVariables struct {
|
||||
InfisicalClient infisicalSdk.InfisicalClientInterface
|
||||
CancelCtx context.CancelFunc
|
||||
AuthDetails AuthenticationDetails
|
||||
}
|
40
k8-operator/packages/util/time.go
Normal file
40
k8-operator/packages/util/time.go
Normal file
@ -0,0 +1,40 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ConvertResyncIntervalToDuration(resyncInterval string) (time.Duration, error) {
|
||||
length := len(resyncInterval)
|
||||
if length < 2 {
|
||||
return 0, fmt.Errorf("invalid format")
|
||||
}
|
||||
|
||||
unit := resyncInterval[length-1:]
|
||||
numberPart := resyncInterval[:length-1]
|
||||
|
||||
number, err := strconv.Atoi(numberPart)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
switch unit {
|
||||
case "s":
|
||||
if number < 5 {
|
||||
return 0, fmt.Errorf("resync interval must be at least 5 seconds")
|
||||
}
|
||||
return time.Duration(number) * time.Second, nil
|
||||
case "m":
|
||||
return time.Duration(number) * time.Minute, nil
|
||||
case "h":
|
||||
return time.Duration(number) * time.Hour, nil
|
||||
case "d":
|
||||
return time.Duration(number) * 24 * time.Hour, nil
|
||||
case "w":
|
||||
return time.Duration(number) * 7 * 24 * time.Hour, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid time unit")
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user