Skip to content

Commit c4d5d68

Browse files
committed
Add show user command
Adds a command to show the contents of the pguser Secrets. Issue: PGO-470
1 parent 8ea30a4 commit c4d5d68

File tree

6 files changed

+281
-2
lines changed

6 files changed

+281
-2
lines changed

docs/content/reference/pgo_show.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,4 +88,5 @@ HA
8888
* [pgo](/reference/) - pgo is a kubectl plugin for PGO, the open source Postgres Operator
8989
* [pgo show backup](/reference/pgo_show_backup/) - Show backup information for a PostgresCluster
9090
* [pgo show ha](/reference/pgo_show_ha/) - Show 'patronictl list' for a PostgresCluster.
91+
* [pgo show user](/reference/pgo_show_user/) - Show pguser Secret details for a PostgresCluster.
9192

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
---
2+
title: pgo show user
3+
---
4+
## pgo show user
5+
6+
Show pguser Secret details for a PostgresCluster.
7+
8+
### Synopsis
9+
10+
Show pguser Secret details for a PostgresCluster.
11+
12+
#### RBAC Requirements
13+
Resources Verbs
14+
--------- -----
15+
secrets [list]
16+
17+
### Usage
18+
19+
```
20+
pgo show user CLUSTER_NAME [flags]
21+
```
22+
23+
### Examples
24+
25+
```
26+
# Show non-sensitive contents of 'pguser' Secret
27+
pgo show user hippo
28+
29+
# Show contents of 'pguser' Secret, including sensitive fields
30+
pgo show user hippo --show-sensitive-fields
31+
32+
```
33+
### Example output
34+
```
35+
pgo show user hippo
36+
SECRET: hippo-pguser-hippo
37+
DBNAME: hippo
38+
HOST: hippo-primary.postgres-operator.svc
39+
PORT: 5432
40+
USER: hippo
41+
42+
```
43+
44+
### Options
45+
46+
```
47+
-h, --help help for user
48+
-f, --show-sensitive-fields show sensitive user fields
49+
```
50+
51+
### Options inherited from parent commands
52+
53+
```
54+
--as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace.
55+
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
56+
--as-uid string UID to impersonate for the operation.
57+
--cache-dir string Default cache directory (default "$HOME/.kube/cache")
58+
--certificate-authority string Path to a cert file for the certificate authority
59+
--client-certificate string Path to a client certificate file for TLS
60+
--client-key string Path to a client key file for TLS
61+
--cluster string The name of the kubeconfig cluster to use
62+
--context string The name of the kubeconfig context to use
63+
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
64+
--kubeconfig string Path to the kubeconfig file to use for CLI requests.
65+
-n, --namespace string If present, the namespace scope for this CLI request
66+
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
67+
-s, --server string The address and port of the Kubernetes API server
68+
--tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used
69+
--token string Bearer token for authentication to the API server
70+
--user string The name of the kubeconfig user to use
71+
```
72+
73+
### SEE ALSO
74+
75+
* [pgo show](/reference/pgo_show/) - Show PostgresCluster details
76+

internal/cmd/show.go

Lines changed: 152 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@ package cmd
1616

1717
import (
1818
"context"
19+
"encoding/base64"
1920
"fmt"
2021
"io"
22+
"os"
23+
"sort"
2124
"strings"
2225

2326
"github.com/spf13/cobra"
27+
corev1 "k8s.io/api/core/v1"
2428
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25-
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
29+
v1 "k8s.io/client-go/kubernetes/typed/core/v1"
30+
"sigs.k8s.io/yaml"
2631

2732
"github.com/crunchydata/postgres-operator-client/internal"
2833
"github.com/crunchydata/postgres-operator-client/internal/util"
@@ -77,6 +82,7 @@ HA
7782
cmdShow.AddCommand(
7883
newShowBackupCommand(config),
7984
newShowHACommand(config),
85+
newShowUserCommand(config),
8086
)
8187

8288
// Limit the number of args, that is, only one cluster name
@@ -278,6 +284,150 @@ func showHA(
278284
return Executor(exec).patronictl("list", output)
279285
}
280286

287+
// newShowUserCommand returns the decoded contents of the cluster's user Secrets.
288+
func newShowUserCommand(config *internal.Config) *cobra.Command {
289+
290+
cmdShowUser := &cobra.Command{
291+
Use: "user CLUSTER_NAME",
292+
Short: "Show pguser Secret details for a PostgresCluster.",
293+
Long: `Show pguser Secret details for a PostgresCluster.
294+
295+
#### RBAC Requirements
296+
Resources Verbs
297+
--------- -----
298+
secrets [list]
299+
300+
### Usage`}
301+
302+
cmdShowUser.Example = internal.FormatExample(`# Show non-sensitive contents of 'pguser' Secret
303+
pgo show user hippo
304+
305+
# Show contents of 'pguser' Secret, including sensitive fields
306+
pgo show user hippo --show-sensitive-fields
307+
308+
### Example output
309+
pgo show user hippo
310+
SECRET: hippo-pguser-hippo
311+
DBNAME: hippo
312+
HOST: hippo-primary.postgres-operator.svc
313+
PORT: 5432
314+
USER: hippo
315+
`)
316+
317+
var fields bool
318+
cmdShowUser.Flags().BoolVarP(&fields, "show-sensitive-fields", "f", false, "show sensitive user fields")
319+
320+
// Limit the number of args, that is, only one cluster name
321+
cmdShowUser.Args = cobra.ExactArgs(1)
322+
323+
// Define the 'show backup' command
324+
cmdShowUser.RunE = func(cmd *cobra.Command, args []string) error {
325+
326+
stdout, err := showUser(config, args, fields)
327+
if err != nil {
328+
return err
329+
}
330+
331+
cmd.Print(stdout)
332+
333+
return nil
334+
}
335+
336+
return cmdShowUser
337+
}
338+
339+
// showUser returns a string with the decoded contents of the cluster's user Secrets.
340+
func showUser(config *internal.Config, args []string, showSensitive bool) (string, error) {
341+
342+
// break out keys based on whether sensitive information is included
343+
var fields = []string{"dbname", "host", "pgbouncer-host", "pgbouncer-port", "port", "user"}
344+
var sensitive = []string{"jdbc-uri", "password", "pgbouncer-jdbc-uri", "pgbouncer-uri", "uri", "verifier"}
345+
346+
if showSensitive {
347+
fields = append(fields, sensitive...)
348+
349+
fmt.Print("WARNING: This command will show sensitive password information." +
350+
"\nAre you sure you want to continue? (yes/no): ")
351+
352+
var confirmed *bool
353+
for i := 0; confirmed == nil && i < 10; i++ {
354+
// retry 10 times or until a confirmation is given or denied,
355+
// whichever comes first
356+
confirmed = confirm(os.Stdin, os.Stdout)
357+
}
358+
359+
if confirmed == nil || !*confirmed {
360+
return "", nil
361+
}
362+
}
363+
364+
// configure client
365+
ctx := context.Background()
366+
rest, err := config.ToRESTConfig()
367+
if err != nil {
368+
return "", err
369+
}
370+
client, err := v1.NewForConfig(rest)
371+
if err != nil {
372+
return "", err
373+
}
374+
375+
// Get the namespace. This will either be from the Kubernetes configuration
376+
// or from the --namespace (-n) flag.
377+
configNamespace, err := config.Namespace()
378+
if err != nil {
379+
return "", err
380+
}
381+
382+
list, err := client.Secrets(configNamespace).List(ctx, metav1.ListOptions{
383+
LabelSelector: util.PostgresUserSecretLabels(args[0]),
384+
})
385+
if err != nil {
386+
return "", err
387+
}
388+
389+
return userData(fields, list)
390+
}
391+
392+
// userData returns the requested user data from the provided Secret List.
393+
// If the Secret List is empty, return a message stating that.
394+
func userData(fields []string, list *corev1.SecretList) (string, error) {
395+
396+
var output string
397+
398+
if len(list.Items) == 0 {
399+
output += fmt.Sprintln("No user Secrets found.")
400+
}
401+
402+
for _, secret := range list.Items {
403+
output += fmt.Sprintf("SECRET: %s\n", secret.Name)
404+
405+
// sort keys
406+
keys := make([]string, 0, len(secret.Data))
407+
for k := range secret.Data {
408+
keys = append(keys, k)
409+
}
410+
sort.Strings(keys)
411+
412+
// decode and print keys and values from Secret
413+
for _, k := range keys {
414+
b, err := yaml.Marshal(secret.Data[k])
415+
if err != nil {
416+
return output, err
417+
}
418+
d := make([]byte, base64.StdEncoding.EncodedLen(len(b)))
419+
_, err = base64.StdEncoding.Decode(d, b)
420+
if err != nil {
421+
return output, err
422+
}
423+
if containsString(fields, k) {
424+
output += fmt.Sprintf(" %s: %s\n", strings.ToUpper(k), string(d))
425+
}
426+
}
427+
}
428+
return output, nil
429+
}
430+
281431
// getPrimaryExec returns a executor function for the primary Pod to allow for
282432
// commands to be run against it.
283433
func getPrimaryExec(config *internal.Config, args []string) (
@@ -291,7 +441,7 @@ func getPrimaryExec(config *internal.Config, args []string) (
291441
if err != nil {
292442
return nil, err
293443
}
294-
client, err := corev1.NewForConfig(rest)
444+
client, err := v1.NewForConfig(rest)
295445
if err != nil {
296446
return nil, err
297447
}

internal/util/naming.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ const (
4646
// RolePatroniLeader is the LabelRole that Patroni sets on the Pod that is
4747
// currently the leader.
4848
RolePatroniLeader = "master"
49+
50+
// RolePostgresUser is the LabelRole applied to PostgreSQL user secrets.
51+
RolePostgresUser = "pguser"
4952
)
5053

5154
const (
@@ -62,3 +65,9 @@ func PrimaryInstanceLabels(clusterName string) string {
6265
LabelData + "=" + DataPostgres + "," +
6366
LabelRole + "=" + RolePatroniLeader
6467
}
68+
69+
// PostgresUserSecretLabels provides labels for the Postgres user Secret
70+
func PostgresUserSecretLabels(clusterName string) string {
71+
return LabelCluster + "=" + clusterName + "," +
72+
LabelRole + "=" + RolePostgresUser
73+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
apiVersion: kuttl.dev/v1beta1
2+
kind: TestStep
3+
commands:
4+
- script: |
5+
PRIMARY=$(
6+
kubectl get pod --namespace "${NAMESPACE}" \
7+
--output name --selector '
8+
postgres-operator.crunchydata.com/cluster=show-cluster,
9+
postgres-operator.crunchydata.com/role=master'
10+
)
11+
12+
CLI_USER=$(
13+
kubectl-pgo --namespace "${NAMESPACE}" show user show-cluster
14+
)
15+
16+
status=$?
17+
if [ $status -ne 0 ]; then
18+
echo "pgo command unsuccessful"
19+
exit 1
20+
fi
21+
22+
# expected output
23+
SHOW_USER_OUTPUT="SECRET: show-cluster-pguser-show-cluster
24+
DBNAME: show-cluster
25+
HOST: show-cluster-primary.${NAMESPACE}.svc
26+
PORT: 5432
27+
USER: show-cluster"
28+
29+
# check command output is not empty and equals the expected output
30+
if [[ -z $CLI_USER && "$CLI_USER" != "$SHOW_USER_OUTPUT" ]]; then
31+
exit 1
32+
fi
33+
34+
CLI_USER_SENSITIVE=$(
35+
echo yes | kubectl-pgo --namespace "${NAMESPACE}" show user show-cluster --show-sensitive-fields
36+
)
37+
38+
# check command output is not empty and contains the password field
39+
if [[ -n $CLI_USER_SENSITIVE && "$CLI_USER_SENSITIVE" == *"PASSWORD:"* ]]; then
40+
exit 0
41+
fi
42+
43+
exit 1
File renamed without changes.

0 commit comments

Comments
 (0)