package admin import ( "crypto/x509" "errors" "fmt" "io" "io/ioutil" "os" "github.com/openshift/origin/pkg/cmd/util/term" "github.com/spf13/cobra" kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" configapi "github.com/openshift/origin/pkg/cmd/server/api" "github.com/openshift/origin/pkg/cmd/templates" pemutil "github.com/openshift/origin/pkg/cmd/util/pem" ) const DecryptCommandName = "decrypt" type DecryptOptions struct { // EncryptedFile is a file containing an encrypted PEM block. EncryptedFile string // EncryptedData is a byte slice containing an encrypted PEM block. EncryptedData []byte // EncryptedReader is used to read an encrypted PEM block if no EncryptedFile or EncryptedData is provided. Cannot be a terminal reader. EncryptedReader io.Reader // DecryptedFile is a destination file to write decrypted data to. DecryptedFile string // DecryptedWriter is used to write decrypted data to if no DecryptedFile is provided DecryptedWriter io.Writer // KeyFile is a file containing a PEM block with the password to use to decrypt the data KeyFile string } var decryptExample = templates.Examples(` # Decrypt an encrypted file to a cleartext file: %[1]s --key=secret.key --in=secret.encrypted --out=secret.decrypted # Decrypt from stdin to stdout: %[1]s --key=secret.key < secret2.encrypted > secret2.decrypted`) func NewCommandDecrypt(commandName string, fullName, encryptFullName string, out io.Writer) *cobra.Command { options := &DecryptOptions{ EncryptedReader: os.Stdin, DecryptedWriter: out, } cmd := &cobra.Command{ Use: commandName, Short: fmt.Sprintf("Decrypt data encrypted with %q", encryptFullName), Example: fmt.Sprintf(decryptExample, fullName), Run: func(cmd *cobra.Command, args []string) { kcmdutil.CheckErr(options.Validate(args)) kcmdutil.CheckErr(options.Decrypt()) }, } flags := cmd.Flags() flags.StringVar(&options.EncryptedFile, "in", options.EncryptedFile, fmt.Sprintf("File containing encrypted data, in the format written by %q.", encryptFullName)) flags.StringVar(&options.DecryptedFile, "out", options.DecryptedFile, "File to write the decrypted data to. Written to stdout if omitted.") flags.StringVar(&options.KeyFile, "key", options.KeyFile, fmt.Sprintf("The file to read the decrypting key from. Must be a PEM file in the format written by %q.", encryptFullName)) // autocompletion hints cmd.MarkFlagFilename("in") cmd.MarkFlagFilename("out") cmd.MarkFlagFilename("key") return cmd } func (o *DecryptOptions) Validate(args []string) error { if len(args) != 0 { return errors.New("no arguments are supported") } if len(o.EncryptedFile) == 0 && len(o.EncryptedData) == 0 && (o.EncryptedReader == nil || term.IsTerminalReader(o.EncryptedReader)) { return errors.New("no input data specified") } if len(o.EncryptedFile) > 0 && len(o.EncryptedData) > 0 { return errors.New("cannot specify both an input file and data") } if len(o.KeyFile) == 0 { return errors.New("no key specified") } return nil } func (o *DecryptOptions) Decrypt() error { // Get PEM data block var data []byte switch { case len(o.EncryptedFile) > 0: if d, err := ioutil.ReadFile(o.EncryptedFile); err != nil { return err } else { data = d } case len(o.EncryptedData) > 0: data = o.EncryptedData case o.EncryptedReader != nil && !term.IsTerminalReader(o.EncryptedReader): if d, err := ioutil.ReadAll(o.EncryptedReader); err != nil { return err } else { data = d } } if len(data) == 0 { return fmt.Errorf("no input data specified") } dataBlock, ok := pemutil.BlockFromBytes(data, configapi.StringSourceEncryptedBlockType) if !ok { return fmt.Errorf("input does not contain a valid PEM block of type %q", configapi.StringSourceEncryptedBlockType) } // Get password keyBlock, ok, err := pemutil.BlockFromFile(o.KeyFile, configapi.StringSourceKeyBlockType) if err != nil { return err } if !ok { return fmt.Errorf("%s does not contain a valid PEM block of type %q", o.KeyFile, configapi.StringSourceKeyBlockType) } if len(keyBlock.Bytes) == 0 { return fmt.Errorf("%s does not contain a key", o.KeyFile) } password := keyBlock.Bytes // Decrypt plaintext, err := x509.DecryptPEMBlock(dataBlock, password) if err != nil { return err } // Write decrypted data switch { case len(o.DecryptedFile) > 0: if err := ioutil.WriteFile(o.DecryptedFile, plaintext, os.FileMode(0600)); err != nil { return err } case o.DecryptedWriter != nil: fmt.Fprint(o.DecryptedWriter, string(plaintext)) if term.IsTerminalWriter(o.DecryptedWriter) { fmt.Fprintln(o.DecryptedWriter) } } return nil }