pull/32647/merge
Retr0-XD 2 days ago committed by GitHub
commit 35d859eca6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -18,6 +18,8 @@ package engine
import (
"bytes"
"crypto/sha1"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
@ -31,6 +33,7 @@ import (
"github.com/BurntSushi/toml"
"github.com/Masterminds/sprig/v3"
"golang.org/x/crypto/bcrypt"
"sigs.k8s.io/yaml"
goYaml "sigs.k8s.io/yaml/goyaml.v3"
)
@ -53,6 +56,10 @@ func funcMap() template.FuncMap {
delete(f, "env")
delete(f, "expandenv")
// Override the sprig "htpasswd" function so it accepts an optional
// third argument to select the hash algorithm.
f["htpasswd"] = htpasswd
// Add some extra functionality
extra := template.FuncMap{
"toToml": toTOML,
@ -99,6 +106,38 @@ func funcMap() template.FuncMap {
return f
}
// htpasswd takes a username and password and returns an htpasswd entry. It
// extends the sprig-provided function (see https://github.com/helm/helm/issues/31924)
// by accepting an optional third argument to select the hash algorithm.
//
// Supported algorithms are "bcrypt" (the default, with the same cost as
// sprig) and "sha" ({SHA} base64-encoded SHA-1). Unlike a panic, invalid
// input is reported by returning a descriptive error string.
func htpasswd(username, password string, algorithm ...string) string {
if strings.Contains(username, ":") {
return fmt.Sprintf("invalid username: %s", username)
}
alg := "bcrypt"
if len(algorithm) > 0 {
alg = algorithm[0]
}
switch alg {
case "bcrypt":
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return fmt.Sprintf("failed to encrypt string with bcrypt: %s", err)
}
return fmt.Sprintf("%s:%s", username, hash)
case "sha":
h := sha1.Sum([]byte(password))
return fmt.Sprintf("%s:{SHA}%s", username, base64.StdEncoding.EncodeToString(h[:]))
default:
return fmt.Sprintf("invalid hash algorithm: %s", alg)
}
}
// toYAML takes an interface, marshals it to yaml, and returns a string. It will
// always return a string, even on marshal error (empty string).
//

@ -195,6 +195,54 @@ keyInElement1 = "valueInElement1"`,
}
}
func TestHtpasswd(t *testing.T) {
tests := []struct {
name string
tpl string
expect string
// if non-empty, the output must have this prefix instead of matching expect exactly
prefix string
}{
{
name: "defaults to bcrypt",
tpl: `{{ htpasswd "user" "pass" }}`,
prefix: "user:$2",
},
{
name: "explicit bcrypt",
tpl: `{{ htpasswd "user" "pass" "bcrypt" }}`,
prefix: "user:$2",
},
{
name: "sha produces {SHA} base64 hash",
tpl: `{{ htpasswd "user" "pass" "sha" }}`,
expect: "user:{SHA}nU4eI71bcnBGqeO0t9tXvY1u5oQ=",
},
{
name: "username containing colon is rejected",
tpl: `{{ htpasswd "bad:user" "pass" }}`,
expect: "invalid username: bad:user",
},
{
name: "unsupported algorithm returns error string",
tpl: `{{ htpasswd "user" "pass" "md5" }}`,
expect: "invalid hash algorithm: md5",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var b strings.Builder
require.NoError(t, template.Must(template.New("test").Funcs(funcMap()).Parse(tt.tpl)).Execute(&b, nil), tt.tpl)
if tt.prefix != "" {
assert.Truef(t, strings.HasPrefix(b.String(), tt.prefix), "expected output to start with %q, got %q", tt.prefix, b.String())
return
}
assert.Equal(t, tt.expect, b.String(), tt.tpl)
})
}
}
func TestDurationHelpers(t *testing.T) {
tests := []struct {
name string

Loading…
Cancel
Save