mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-01 11:47:07 -06:00
954d38e870
In prior versions, we recommended using hash functions in conjunction with the file function as an idiom for detecting changes to upstream blobs without fetching and comparing the whole blob. That approach relied on us being able to return raw binary data from file(...). Since Terraform strings pass through intermediate representations that are not binary-safe (e.g. the JSON state), there was a risk of string corruption in prior versions which we have avoided for 0.12 by requiring that file(...) be used only with UTF-8 text files. The specific case of returning a string and immediately passing it into another function was not actually subject to that corruption risk, since the HIL interpreter would just pass the string through verbatim, but this is still now forbidden as a result of the stricter handling of file(...). To avoid breaking these use-cases, here we introduce variants of the hash functions a with "file" prefix that take a filename for a disk file to hash rather than hashing the given string directly. The configuration upgrade tool also now includes a rule to detect the documented idiom and rewrite it into a single function call for one of these new functions. This does cause a bit of function sprawl, but that seems preferable to introducing more complex rules for when file(...) can and cannot read binary files, making the behavior of these various functions easier to understand in isolation.
286 lines
9.4 KiB
Go
286 lines
9.4 KiB
Go
package funcs
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"crypto/rsa"
|
|
"crypto/sha1"
|
|
"crypto/sha256"
|
|
"crypto/sha512"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"encoding/hex"
|
|
"encoding/pem"
|
|
"fmt"
|
|
"hash"
|
|
|
|
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"
|
|
)
|
|
|
|
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
|
|
},
|
|
})
|
|
|
|
// 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, sha512.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), fmt.Errorf("failed to decode input %q: cipher text must be base64-encoded", s)
|
|
}
|
|
|
|
block, _ := pem.Decode([]byte(key))
|
|
if block == nil {
|
|
return cty.UnknownVal(cty.String), fmt.Errorf("failed to parse key: no key found")
|
|
}
|
|
if block.Headers["Proc-Type"] == "4,ENCRYPTED" {
|
|
return cty.UnknownVal(cty.String), fmt.Errorf(
|
|
"failed to parse key: password protected keys are not supported. Please decrypt the key prior to use",
|
|
)
|
|
}
|
|
|
|
x509Key, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
if err != nil {
|
|
return cty.UnknownVal(cty.String), err
|
|
}
|
|
|
|
out, err := rsa.DecryptPKCS1v15(nil, x509Key, b)
|
|
if err != nil {
|
|
return cty.UnknownVal(cty.String), 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()
|
|
src, err := readFileBytes(baseDir, path)
|
|
if err != nil {
|
|
return cty.UnknownVal(cty.String), err
|
|
}
|
|
|
|
h := hf()
|
|
h.Write(src)
|
|
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)
|
|
}
|
|
|
|
// 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})
|
|
}
|