Skip to content

Commit

Permalink
Enforce FIPS compatible Sprig functions for template rendering (#2708)
Browse files Browse the repository at this point in the history
* Add fipsonly sprig support

* Add tests for fipsonly sprig

* Update usages of sprig

* Update docs about rendering templates

* Update docs/templates.rst

Co-authored-by: Pavan Navarathna <6504783+pavannd1@users.noreply.github.com>

* Update pkg/ksprig/fipsonly_sprig.go

Co-authored-by: Pavan Navarathna <6504783+pavannd1@users.noreply.github.com>

* Update pkg/ksprig/fipsonly_sprig_test.go

Co-authored-by: Pavan Navarathna <6504783+pavannd1@users.noreply.github.com>

* Rename error and error field

* Convert tests to check.v1 format

---------

Co-authored-by: Pavan Navarathna <6504783+pavannd1@users.noreply.github.com>
  • Loading branch information
sukhil-suresh and pavannd1 committed Mar 4, 2024
1 parent 282c5bd commit af11989
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 9 deletions.
4 changes: 4 additions & 0 deletions docs/templates.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ standard go template functions, Kanister imports all the `sprig
}
return ras, nil
.. note:: Kanister will error during template rendering if FIPS non-compliant
sprig functions are used. The unsupported functions include: ``bcrypt``,
``derivePassword``, ``htpasswd``, and ``genPrivateKey`` for key type ``dsa``.


Objects
=======
Expand Down
4 changes: 2 additions & 2 deletions pkg/function/wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"text/template"
"time"

"github.com/Masterminds/sprig"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -33,6 +32,7 @@ import (
crv1alpha1 "github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1"
"github.com/kanisterio/kanister/pkg/field"
"github.com/kanisterio/kanister/pkg/jsonpath"
"github.com/kanisterio/kanister/pkg/ksprig"
"github.com/kanisterio/kanister/pkg/kube"
"github.com/kanisterio/kanister/pkg/log"
"github.com/kanisterio/kanister/pkg/param"
Expand Down Expand Up @@ -189,7 +189,7 @@ func evaluateWaitCondition(ctx context.Context, dynCli dynamic.Interface, cond C
return false, err
}
log.Debug().Print(fmt.Sprintf("Resolved jsonpath: %s", rcondition))
t, err := template.New("config").Option("missingkey=zero").Funcs(sprig.TxtFuncMap()).Parse(rcondition)
t, err := template.New("config").Option("missingkey=zero").Funcs(ksprig.TxtFuncMap()).Parse(rcondition)
if err != nil {
return false, errors.WithStack(err)
}
Expand Down
80 changes: 80 additions & 0 deletions pkg/ksprig/fipsonly_sprig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2024 The Kanister Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ksprig

import (
"fmt"
"html/template"

"github.com/Masterminds/sprig"
)

// TxtFuncMap provides a FIPS compliant version of sprig.TxtFuncMap().
// Usage of a FIPS non-compatible function from the function map will result in an error.
func TxtFuncMap() template.FuncMap {
return replaceNonCompliantFuncs(sprig.TxtFuncMap())
}

func replaceNonCompliantFuncs(m map[string]interface{}) map[string]interface{} {
for name, fn := range fipsNonCompliantFuncs {
if _, ok := m[name]; ok {
m[name] = fn
}
}
return m
}

// fipsNonCompliantFuncs is a map of sprig function name to its replacement function.
// Functions identified for Sprig v3.2.3.
var fipsNonCompliantFuncs = map[string]interface{}{
"bcrypt": func(input string) (string, error) {
return "", NewUnsupportedSprigUsageErr("bcrypt")
},

"derivePassword": func(counter uint32, password_type, password, user, site string) (string, error) {
return "", NewUnsupportedSprigUsageErr("derivePassword")
},

"genPrivateKey": func(typ string) (string, error) {
switch typ {
case "rsa", "ecdsa", "ed25519":
fn, ok := sprig.TxtFuncMap()["genPrivateKey"].(func(string) string)
if !ok {
return "", NewUnsupportedSprigUsageErr("genPrivateKey")
}
return fn(typ), nil
}
return "", NewUnsupportedSprigUsageErr(fmt.Sprintf("genPrivateKey for %s", typ))
},

"htpasswd": func(username string, password string) (string, error) {
return "", NewUnsupportedSprigUsageErr("htpasswd")
},
}

// NewUnsupportedSprigUsageErr returns an UnsupportedSprigUsageErr.
func NewUnsupportedSprigUsageErr(usage string) UnsupportedSprigUsageErr {
return UnsupportedSprigUsageErr{Usage: usage}
}

// UnsupportedSprigUsageErr indicates a FIPS non-compatible sprig usage.
type UnsupportedSprigUsageErr struct {
Usage string
}

// Error returns an error string indicating the unsupported function.
func (err UnsupportedSprigUsageErr) Error() string {
return fmt.Sprintf("sprig usage of '%s' is not allowed by kanister as it is not FIPS compatible", err.Usage)
}
121 changes: 121 additions & 0 deletions pkg/ksprig/fipsonly_sprig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Copyright 2024 The Kanister Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ksprig_test

import (
"errors"
"strings"
"testing"
"text/template"

. "gopkg.in/check.v1"

"github.com/kanisterio/kanister/pkg/ksprig"
)

type FipsOnlySprigSuite struct{}

var _ = Suite(&FipsOnlySprigSuite{})

func TestFipsOnlySprigSuite(t *testing.T) { TestingT(t) }

func (f *FipsOnlySprigSuite) TestUnsupportedTxtFuncMapUsage(c *C) {
funcMap := ksprig.TxtFuncMap()

testCases := []struct {
function string
templateText string
usageErr string
}{
{
function: "bcrypt",
templateText: "{{bcrypt \"password\"}}",
usageErr: "bcrypt",
},
{
function: "derivePassword",
templateText: "{{derivePassword 1 \"long\" \"password\" \"user\" \"example.com\"}}",
usageErr: "derivePassword",
},
{
function: "genPrivateKey",
templateText: "{{genPrivateKey \"dsa\"}}",
usageErr: "genPrivateKey for dsa",
},
{
function: "htpasswd",
templateText: "{{htpasswd \"username\" \"password\"}}",
usageErr: "htpasswd",
},
}

for _, tc := range testCases {
if _, ok := funcMap[tc.function]; !ok {
c.Logf("Skipping test of %s since the tested sprig version does not support it", tc.function)
continue
}
c.Logf("Testing %s", tc.function)

temp, err := template.New("test").Funcs(funcMap).Parse(tc.templateText)
c.Assert(err, IsNil)

err = temp.Execute(nil, "")

var sprigErr ksprig.UnsupportedSprigUsageErr
c.Assert(errors.As(err, &sprigErr), Equals, true)
c.Assert(sprigErr.Usage, Equals, tc.usageErr)
}
}

func (f *FipsOnlySprigSuite) TestSupportedTxtFuncMapUsage(c *C) {
funcMap := ksprig.TxtFuncMap()

testCases := []struct {
description string
function string
templateText string
}{
// The supported usages are not limited to these test cases
{
description: "genPrivateKey for rsa key",
function: "genPrivateKey",
templateText: "{{genPrivateKey \"rsa\"}}",
},
{
description: "genPrivateKey for ecdsa key",
function: "genPrivateKey",
templateText: "{{genPrivateKey \"ecdsa\"}}",
},
{
description: "genPrivateKey for ed25519 key",
function: "genPrivateKey",
templateText: "{{genPrivateKey \"ed25519\"}}",
},
}

for _, tc := range testCases {
if _, ok := funcMap[tc.function]; !ok {
c.Logf("Skipping test of %s since the tested sprig version does not support it", tc.function)
continue
}
c.Logf("Testing %s", tc.description)

temp, err := template.New("test").Funcs(funcMap).Parse(tc.templateText)
c.Assert(err, IsNil)

err = temp.Execute(&strings.Builder{}, "")
c.Assert(err, IsNil)
}
}
6 changes: 3 additions & 3 deletions pkg/kube/unstructured_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ import (
"text/template"

. "gopkg.in/check.v1"

"github.com/Masterminds/sprig"
"k8s.io/apimachinery/pkg/runtime/schema"

"github.com/kanisterio/kanister/pkg/ksprig"
)

type UnstructuredSuite struct{}
Expand Down Expand Up @@ -52,7 +52,7 @@ func (s *UnstructuredSuite) TestFetch(c *C) {
{"{{ .Unstructured.metadata.name }}"},
{"{{ .Unstructured.spec.clusterIP }}"},
} {
t, err := template.New("config").Option("missingkey=error").Funcs(sprig.TxtFuncMap()).Parse(tc.arg)
t, err := template.New("config").Option("missingkey=error").Funcs(ksprig.TxtFuncMap()).Parse(tc.arg)
c.Assert(err, IsNil)
err = t.Execute(buf, tp)
c.Assert(err, IsNil)
Expand Down
4 changes: 2 additions & 2 deletions pkg/param/param_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import (
"text/template"
"time"

"github.com/Masterminds/sprig"
. "gopkg.in/check.v1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
Expand All @@ -38,6 +37,7 @@ import (

crv1alpha1 "github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1"
crfake "github.com/kanisterio/kanister/pkg/client/clientset/versioned/fake"
"github.com/kanisterio/kanister/pkg/ksprig"
"github.com/kanisterio/kanister/pkg/kube"
osapps "github.com/openshift/api/apps/v1"
osversioned "github.com/openshift/client-go/apps/clientset/versioned"
Expand Down Expand Up @@ -772,7 +772,7 @@ func (s *ParamsSuite) TestRenderingPhaseParams(c *C) {
"bar",
},
} {
t, err := template.New("config").Option("missingkey=error").Funcs(sprig.TxtFuncMap()).Parse(tc.arg)
t, err := template.New("config").Option("missingkey=error").Funcs(ksprig.TxtFuncMap()).Parse(tc.arg)
c.Assert(err, IsNil)
buf := bytes.NewBuffer(nil)
err = t.Execute(buf, tp)
Expand Down
4 changes: 2 additions & 2 deletions pkg/param/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import (
"strings"
"text/template"

"github.com/Masterminds/sprig"
"github.com/pkg/errors"

crv1alpha1 "github.com/kanisterio/kanister/pkg/apis/cr/v1alpha1"
"github.com/kanisterio/kanister/pkg/ksprig"
)

const (
Expand Down Expand Up @@ -119,7 +119,7 @@ func RenderArtifacts(arts map[string]crv1alpha1.Artifact, tp TemplateParams) (ma
}

func renderStringArg(arg string, tp TemplateParams) (string, error) {
t, err := template.New("config").Option("missingkey=error").Funcs(sprig.TxtFuncMap()).Parse(arg)
t, err := template.New("config").Option("missingkey=error").Funcs(ksprig.TxtFuncMap()).Parse(arg)
if err != nil {
return "", errors.WithStack(err)
}
Expand Down

0 comments on commit af11989

Please sign in to comment.