Skip to content

Commit

Permalink
Allow P2F custom templates via ConfigMap
Browse files Browse the repository at this point in the history
  • Loading branch information
john-odonnell committed Nov 24, 2021
1 parent 25b2bc5 commit cf9572a
Show file tree
Hide file tree
Showing 11 changed files with 421 additions and 41 deletions.
82 changes: 76 additions & 6 deletions PUSH_TO_FILE.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ Push to File operation.
- `/conjur/podinfo` for the `podinfo` volume.
- `/conjur/secrets` for the `conjur-secrets` volume.
- `/conjur/templates` for the `conjur-templates` volume.
```
Expand Down Expand Up @@ -263,6 +264,8 @@ Push to File operation.
mountPath: /conjur/podinfo
- name: conjur-secrets
mountPath: /conjur/secrets
- name: conjur-templates
mountPath: /conjur/templates
volumes:
- name: podinfo
downwardAPI:
Expand All @@ -273,6 +276,9 @@ Push to File operation.
- name: conjur-secrets
emptyDir:
medium: Memory
- name: conjur-templates
emptyDir:
medium: Memory
```
The Secrets Provider will create a secret file in the `conjur-secrets`
Expand Down Expand Up @@ -307,7 +313,7 @@ for a description of each environment variable setting:
| `conjur.org/conjur-secrets.{secret-group}` | Note\* | List of secrets to be retrieved from Conjur. Each entry can be either:<ul><li>A Conjur variable path</li><li> A key/value pairs of the form `<alias>:<Conjur variable path>` where the `alias` represents the name of the secret to be written to the secrets file |
| `conjur.org/conjur-secrets-policy-path.{secret-group}` | Note\* | Defines a common Conjur policy path, assumed to be relative to the root policy.<br><br>When this annotation is set, the policy paths defined by `conjur.org/conjur-secrets.{secret-group}` are relative to this common path.<br><br>When this annotation is not set, the policy paths defined by `conjur.org/conjur-secrets.{secret-group}` are themselves relative to the root policy.<br><br>(See [Example Common Policy Path](#example-common-policy-path) for an explicit example of this relationship.)|
| `conjur.org/secret-file-path.{secret-group}` | Note\* | Relative path for secret file or directory to be written. This path is assumed to be relative to the respective mount path for the shared secrets volume for each container.<br><br>If the `conjur.org/secret-file-template.{secret-group}` is set, then this secret file path must also be set, and it must include a file name (i.e. must not end in `/`).<br><br>If the `conjur.org/secret-file-template.{secret-group}` is not set, then this secret file path defaults to `{secret-group}.{secret-group-file-format}`. For example, if the secret group name is `my-app`, and the secret file format is set for YAML, the the secret file path defaults to `my-app.yaml`.
| `conjur.org/secret-file-format.{secret-group}` | Note\* | Allowed values:<ul><li>yaml (default)</li><li>json</li><li>dotenv</li><li>bash</li></ul>(See [Example Secret File Formats](#example-secret-file-formats) for example output files.) |
| `conjur.org/secret-file-format.{secret-group}` | Note\* | Allowed values:<ul><li>yaml (default)</li><li>json</li><li>dotenv</li><li>bash</li><li>template</li></ul><br>This annotation must be set to `template` when using custom templates.<br><br>(See [Example Secret File Formats](#example-secret-file-formats) for example output files.) |
| `conjur.org/secret-file-template.{secret-group}`| Note\* | Defines a custom template in Golang text template format with which to render secret file content. See dedicated [Custom Templates for Secret Files](#custom-templates-for-secret-files) section for details. |
__Note*:__ These Push to File annotations do not have an equivalent
Expand Down Expand Up @@ -387,11 +393,75 @@ admin-password="dev/redis/password"
## Custom Templates for Secret Files
In addition to offering standard file formats, Push to File allows users to
define their own custom secret file templates, configured with the
`conjur.org/secret-file-template.{secret-group}` annotation. These templates
adhere to Go's text template formatting. Providing a custom template will
override the use of any standard format configured with the annotation
`conjur.org/secret-file-format.{secret-group}`.
define their own custom secret file templates. These templates adhere to Go's
text template formatting. Providing custom templates requires setting the
annotation `conjur.org/secret-file-format.{secret-group}` to `"template"`.
Custom templates can be provided either by explicit assignment through Pod
annotations, or through a volume-mounted ConfigMap.
1. <details><summary>Pod annotation</summary>
Custom templates can be defined with the Pod annotation
`conjur.org/secret-file-template.{secret-group}`. The following annotations
describe a valid secret group that uses a custom template defined in Pod
annotations:
```
conjur.org/secret-group.example: |
- admin-username: <variable-policy-path>
- admin-password: <variable-policy-path>
conjur.org/secret-file-format.example: "template"
conjur.org/secret-file-template.example: |
"database": {
"username": {{ secret "admin-username" }},
"password": {{ secret "admin-password" }},
}
```
</details>
2. <details><summary>Volume-mounted ConfigMap</summary>
Custom templates can be provided as fields in a ConfigMap. This feature
requires the following:
- The ConfigMap containing template files must be mounted in the Secrets
Provider container at `/conjur/templates`
- Each template's key in the ConfigMap's `data` field must be formatted as
`{secret-group}.tpl`.
The following is an example of a ConfigMap defining a custom Go template for
a secret group `example`:
```
apiVersion: v1
kind: ConfigMap
metadata:
name: my-custom-template
data:
example.tpl: |
"database": {
"username": {{ secret "admin-username" }},
"password": {{ secret "admin-password" }},
}
```
The following annotations describe a valid secret group that uses a custom
template defined in the above ConfigMap:
```
conjur.org/secret-group.example: |
- admin-username: <variable-policy-path>
- admin-password: <variable-policy-path>
conjur.org/secret-file-format.example: "template"
```
</details>
Providing a template with both methods for a single secret group fails before
retrieving secrets from Conjur.
### Using Conjur Secrets in Custom Templates
Injecting Conjur secrets into custom templates requires using the custom
template function `secret`. The action shown below renders the value associated
Expand Down
12 changes: 7 additions & 5 deletions cmd/secrets-provider/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
defaultContainerMode = "init"
annotationsFilePath = "/conjur/podinfo/annotations"
secretsBasePath = "/conjur/secrets"
templatesBasePath = "/conjur/templates"
)

var annotationsMap map[string]string
Expand Down Expand Up @@ -62,11 +63,12 @@ func main() {
}

providerConfig := secrets.ProviderConfig{
StoreType: secretsConfig.StoreType,
PodNamespace: secretsConfig.PodNamespace,
RequiredK8sSecrets: secretsConfig.RequiredK8sSecrets,
SecretFileBasePath: secretsBasePath,
AnnotationsMap: annotationsMap,
StoreType: secretsConfig.StoreType,
PodNamespace: secretsConfig.PodNamespace,
RequiredK8sSecrets: secretsConfig.RequiredK8sSecrets,
SecretFileBasePath: secretsBasePath,
TemplateFileBasePath: templatesBasePath,
AnnotationsMap: annotationsMap,
}
provideSecrets, errs := secrets.NewProviderForType(
secretRetriever.Retrieve,
Expand Down
2 changes: 1 addition & 1 deletion pkg/secrets/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ var pushToFileAnnotationPrefixes = map[string]annotationRestraints{
"conjur.org/conjur-secrets.": {TYPESTRING, []string{}},
"conjur.org/conjur-secrets-policy-path.": {TYPESTRING, []string{}},
"conjur.org/secret-file-path.": {TYPESTRING, []string{}},
"conjur.org/secret-file-format.": {TYPESTRING, []string{"yaml", "json", "dotenv", "bash"}},
"conjur.org/secret-file-format.": {TYPESTRING, []string{"yaml", "json", "dotenv", "bash", "template"}},
"conjur.org/secret-file-template": {TYPESTRING, []string{}},
}

Expand Down
6 changes: 4 additions & 2 deletions pkg/secrets/provide_conjur_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ type ProviderConfig struct {
RequiredK8sSecrets []string

// Config specific to Push to File provider
SecretFileBasePath string
AnnotationsMap map[string]string
SecretFileBasePath string
TemplateFileBasePath string
AnnotationsMap map[string]string
}

// ProviderFunc describes a function type responsible for providing secrets to an unspecified target.
Expand All @@ -50,6 +51,7 @@ func NewProviderForType(
provider, err := pushtofile.NewProvider(
secretsRetrieverFunc,
providerConfig.SecretFileBasePath,
providerConfig.TemplateFileBasePath,
providerConfig.AnnotationsMap,
)
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions pkg/secrets/pushtofile/provide_conjur_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ type fileProvider struct {
}

// NewProvider creates a new provider for Push-to-File mode.
func NewProvider(retrieveSecretsFunc conjur.RetrieveSecretsFunc, secretsBasePath string, annotations map[string]string) (*fileProvider, []error) {
secretGroups, err := NewSecretGroups(secretsBasePath, annotations)
func NewProvider(retrieveSecretsFunc conjur.RetrieveSecretsFunc, secretsBasePath string, templatesBasePath string, annotations map[string]string) (*fileProvider, []error) {
secretGroups, err := NewSecretGroups(secretsBasePath, templatesBasePath, annotations)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/secrets/pushtofile/provide_conjur_secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func TestNewProvider(t *testing.T) {

for _, tc := range TestCases {
t.Run(tc.description, func(t *testing.T) {
p, err := NewProvider(tc.retrieveFunc, tc.basePath, tc.annotations)
p, err := NewProvider(tc.retrieveFunc, tc.basePath, "", tc.annotations)
assert.Empty(t, err)
assert.Equal(t, tc.expectedSecretGroup, p.secretGroups)
})
Expand Down
33 changes: 33 additions & 0 deletions pkg/secrets/pushtofile/pull_from_reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package pushtofile

import (
"io"
"io/ioutil"
"os"
)

type pullFromReaderFunc func(
reader io.Reader,
) (string, error)

type openReadCloserFunc func(
path string,
) (io.ReadCloser, error)

func openFileAsReadCloser(path string) (io.ReadCloser, error) {
reader, err := os.Open(path)
if err != nil {
return nil, err
}
return ioutil.NopCloser(reader), nil
}

func pullFromReader(
reader io.Reader,
) (string, error) {
content, err := ioutil.ReadAll(reader)
if err != nil {
return "", err
}
return string(content), nil
}
34 changes: 34 additions & 0 deletions pkg/secrets/pushtofile/pull_from_reader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package pushtofile

import (
"bytes"
"testing"
)

type pullFromReaderTestCase struct {
description string
content string
assert func(*testing.T, string, error)
}

func (tc pullFromReaderTestCase) Run(t *testing.T) {
t.Run(tc.description, func(t *testing.T) {
buf := bytes.NewBufferString(tc.content)
readContent, err := pullFromReader(buf)
tc.assert(t, readContent, err)
})
}

var pullFromReaderTestCases = []pullFromReaderTestCase{
{
description: "happy case",
content: "template file content",
assert: assertGoodOutput("template file content"),
},
}

func TestPullFromReader(t *testing.T) {
for _, tc := range pullFromReaderTestCases {
tc.Run(t)
}
}
59 changes: 59 additions & 0 deletions pkg/secrets/pushtofile/secret-group_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package pushtofile
import (
"bytes"
"io"
"io/ioutil"
"os"
)

Expand Down Expand Up @@ -76,3 +77,61 @@ func (spy *openWriteCloserSpy) Call(path string, permissions os.FileMode) (io.Wr

return spy.writeCloser, spy.err
}

//// pullFromReaderFunc
type pullFromReaderArgs struct {
reader io.Reader
}

type pullFromReaderSpy struct {
args pullFromReaderArgs
err error
_calls int
}

func (spy *pullFromReaderSpy) Call(
reader io.Reader,
) (string, error) {
spy._calls++
// This is to ensure the spy is only ever used once!
if spy._calls > 1 {
panic("spy called more than once")
}

spy.args = pullFromReaderArgs{
reader: reader,
}

content, err := ioutil.ReadAll(reader)
if err != nil {
return string(content), err
}

return string(content), spy.err
}

//// openReadCloserFunc
type openReadCloserArgs struct {
path string
}

type openReadCloserSpy struct {
args openReadCloserArgs
readCloser io.ReadCloser
err error
_calls int
}

func (spy *openReadCloserSpy) Call(path string) (io.ReadCloser, error) {
spy._calls++
// This is to ensure the spy is only ever used once!
if spy._calls > 1 {
panic("spy called more than once")
}

spy.args = openReadCloserArgs{
path: path,
}

return spy.readCloser, spy.err
}
Loading

0 comments on commit cf9572a

Please sign in to comment.