package funcs import ( "crypto/md5" "crypto/rsa" "crypto/sha1" "crypto/sha256" "crypto/sha512" "encoding/asn1" "encoding/base64" "encoding/hex" "fmt" "hash" "io" "strings" uuidv5 "github.com/google/uuid" uuid "github.com/hashicorp/go-uuid" "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/function" "github.com/zclconf/go-cty/cty/gocty" "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/ssh" ) var UUIDFunc = function.New(&function.Spec{ Params: []function.Parameter{}, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { result, err := uuid.GenerateUUID() if err != nil { return cty.UnknownVal(cty.String), err } return cty.StringVal(result), nil }, }) var UUIDV5Func = function.New(&function.Spec{ Params: []function.Parameter{ { Name: "namespace", Type: cty.String, }, { Name: "name", Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { var namespace uuidv5.UUID switch { case args[0].AsString() == "dns": namespace = uuidv5.NameSpaceDNS case args[0].AsString() == "url": namespace = uuidv5.NameSpaceURL case args[0].AsString() == "oid": namespace = uuidv5.NameSpaceOID case args[0].AsString() == "x500": namespace = uuidv5.NameSpaceX500 default: if namespace, err = uuidv5.Parse(args[0].AsString()); err != nil { return cty.UnknownVal(cty.String), fmt.Errorf("uuidv5() doesn't support namespace %s (%v)", args[0].AsString(), err) } } val := args[1].AsString() return cty.StringVal(uuidv5.NewSHA1(namespace, []byte(val)).String()), nil }, }) // Base64Sha256Func constructs a function that computes the SHA256 hash of a given string // and encodes it with Base64. var Base64Sha256Func = makeStringHashFunction(sha256.New, base64.StdEncoding.EncodeToString) // MakeFileBase64Sha256Func constructs a function that is like Base64Sha256Func but reads the // contents of a file rather than hashing a given literal string. func MakeFileBase64Sha256Func(baseDir string) function.Function { return makeFileHashFunction(baseDir, sha256.New, base64.StdEncoding.EncodeToString) } // Base64Sha512Func constructs a function that computes the SHA256 hash of a given string // and encodes it with Base64. var Base64Sha512Func = makeStringHashFunction(sha512.New, base64.StdEncoding.EncodeToString) // MakeFileBase64Sha512Func constructs a function that is like Base64Sha512Func but reads the // contents of a file rather than hashing a given literal string. func MakeFileBase64Sha512Func(baseDir string) function.Function { return makeFileHashFunction(baseDir, sha512.New, base64.StdEncoding.EncodeToString) } // BcryptFunc constructs a function that computes a hash of the given string using the Blowfish cipher. var BcryptFunc = function.New(&function.Spec{ Params: []function.Parameter{ { Name: "str", Type: cty.String, }, }, VarParam: &function.Parameter{ Name: "cost", Type: cty.Number, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { defaultCost := 10 if len(args) > 1 { var val int if err := gocty.FromCtyValue(args[1], &val); err != nil { return cty.UnknownVal(cty.String), err } defaultCost = val } if len(args) > 2 { return cty.UnknownVal(cty.String), fmt.Errorf("bcrypt() takes no more than two arguments") } input := args[0].AsString() out, err := bcrypt.GenerateFromPassword([]byte(input), defaultCost) if err != nil { return cty.UnknownVal(cty.String), fmt.Errorf("error occured generating password %s", err.Error()) } return cty.StringVal(string(out)), nil }, }) // Md5Func constructs a function that computes the MD5 hash of a given string and encodes it with hexadecimal digits. var Md5Func = makeStringHashFunction(md5.New, hex.EncodeToString) // MakeFileMd5Func constructs a function that is like Md5Func but reads the // contents of a file rather than hashing a given literal string. func MakeFileMd5Func(baseDir string) function.Function { return makeFileHashFunction(baseDir, md5.New, hex.EncodeToString) } // RsaDecryptFunc constructs a function that decrypts an RSA-encrypted ciphertext. var RsaDecryptFunc = function.New(&function.Spec{ Params: []function.Parameter{ { Name: "ciphertext", Type: cty.String, }, { Name: "privatekey", Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { s := args[0].AsString() key := args[1].AsString() b, err := base64.StdEncoding.DecodeString(s) if err != nil { return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "failed to decode input %q: cipher text must be base64-encoded", s) } rawKey, err := ssh.ParseRawPrivateKey([]byte(key)) if err != nil { var errStr string switch e := err.(type) { case asn1.SyntaxError: errStr = strings.ReplaceAll(e.Error(), "asn1: syntax error", "invalid ASN1 data in the given private key") case asn1.StructuralError: errStr = strings.ReplaceAll(e.Error(), "asn1: struture error", "invalid ASN1 data in the given private key") default: errStr = fmt.Sprintf("invalid private key: %s", e) } return cty.UnknownVal(cty.String), function.NewArgErrorf(1, errStr) } privateKey, ok := rawKey.(*rsa.PrivateKey) if !ok { return cty.UnknownVal(cty.String), function.NewArgErrorf(1, "invalid private key type %t", rawKey) } out, err := rsa.DecryptPKCS1v15(nil, privateKey, b) if err != nil { return cty.UnknownVal(cty.String), fmt.Errorf("failed to decrypt: %s", err) } return cty.StringVal(string(out)), nil }, }) // Sha1Func contructs a function that computes the SHA1 hash of a given string // and encodes it with hexadecimal digits. var Sha1Func = makeStringHashFunction(sha1.New, hex.EncodeToString) // MakeFileSha1Func constructs a function that is like Sha1Func but reads the // contents of a file rather than hashing a given literal string. func MakeFileSha1Func(baseDir string) function.Function { return makeFileHashFunction(baseDir, sha1.New, hex.EncodeToString) } // Sha256Func contructs a function that computes the SHA256 hash of a given string // and encodes it with hexadecimal digits. var Sha256Func = makeStringHashFunction(sha256.New, hex.EncodeToString) // MakeFileSha256Func constructs a function that is like Sha256Func but reads the // contents of a file rather than hashing a given literal string. func MakeFileSha256Func(baseDir string) function.Function { return makeFileHashFunction(baseDir, sha256.New, hex.EncodeToString) } // Sha512Func contructs a function that computes the SHA512 hash of a given string // and encodes it with hexadecimal digits. var Sha512Func = makeStringHashFunction(sha512.New, hex.EncodeToString) // MakeFileSha512Func constructs a function that is like Sha512Func but reads the // contents of a file rather than hashing a given literal string. func MakeFileSha512Func(baseDir string) function.Function { return makeFileHashFunction(baseDir, sha512.New, hex.EncodeToString) } func makeStringHashFunction(hf func() hash.Hash, enc func([]byte) string) function.Function { return function.New(&function.Spec{ Params: []function.Parameter{ { Name: "str", Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { s := args[0].AsString() h := hf() h.Write([]byte(s)) rv := enc(h.Sum(nil)) return cty.StringVal(rv), nil }, }) } func makeFileHashFunction(baseDir string, hf func() hash.Hash, enc func([]byte) string) function.Function { return function.New(&function.Spec{ Params: []function.Parameter{ { Name: "path", Type: cty.String, }, }, Type: function.StaticReturnType(cty.String), Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { path := args[0].AsString() f, err := openFile(baseDir, path) if err != nil { return cty.UnknownVal(cty.String), err } defer f.Close() h := hf() _, err = io.Copy(h, f) if err != nil { return cty.UnknownVal(cty.String), err } rv := enc(h.Sum(nil)) return cty.StringVal(rv), nil }, }) } // UUID generates and returns a Type-4 UUID in the standard hexadecimal string // format. // // This is not a pure function: it will generate a different result for each // call. It must therefore be registered as an impure function in the function // table in the "lang" package. func UUID() (cty.Value, error) { return UUIDFunc.Call(nil) } // UUIDV5 generates and returns a Type-5 UUID in the standard hexadecimal string // format. func UUIDV5(namespace cty.Value, name cty.Value) (cty.Value, error) { return UUIDV5Func.Call([]cty.Value{namespace, name}) } // Base64Sha256 computes the SHA256 hash of a given string and encodes it with // Base64. // // The given string is first encoded as UTF-8 and then the SHA256 algorithm is applied // as defined in RFC 4634. The raw hash is then encoded with Base64 before returning. // Terraform uses the "standard" Base64 alphabet as defined in RFC 4648 section 4. func Base64Sha256(str cty.Value) (cty.Value, error) { return Base64Sha256Func.Call([]cty.Value{str}) } // Base64Sha512 computes the SHA512 hash of a given string and encodes it with // Base64. // // The given string is first encoded as UTF-8 and then the SHA256 algorithm is applied // as defined in RFC 4634. The raw hash is then encoded with Base64 before returning. // Terraform uses the "standard" Base64 alphabet as defined in RFC 4648 section 4 func Base64Sha512(str cty.Value) (cty.Value, error) { return Base64Sha512Func.Call([]cty.Value{str}) } // Bcrypt computes a hash of the given string using the Blowfish cipher, // returning a string in the Modular Crypt Format // usually expected in the shadow password file on many Unix systems. func Bcrypt(str cty.Value, cost ...cty.Value) (cty.Value, error) { args := make([]cty.Value, len(cost)+1) args[0] = str copy(args[1:], cost) return BcryptFunc.Call(args) } // Md5 computes the MD5 hash of a given string and encodes it with hexadecimal digits. func Md5(str cty.Value) (cty.Value, error) { return Md5Func.Call([]cty.Value{str}) } // RsaDecrypt decrypts an RSA-encrypted ciphertext, returning the corresponding // cleartext. func RsaDecrypt(ciphertext, privatekey cty.Value) (cty.Value, error) { return RsaDecryptFunc.Call([]cty.Value{ciphertext, privatekey}) } // Sha1 computes the SHA1 hash of a given string and encodes it with hexadecimal digits. func Sha1(str cty.Value) (cty.Value, error) { return Sha1Func.Call([]cty.Value{str}) } // Sha256 computes the SHA256 hash of a given string and encodes it with hexadecimal digits. func Sha256(str cty.Value) (cty.Value, error) { return Sha256Func.Call([]cty.Value{str}) } // Sha512 computes the SHA512 hash of a given string and encodes it with hexadecimal digits. func Sha512(str cty.Value) (cty.Value, error) { return Sha512Func.Call([]cty.Value{str}) }