karmada-operator: add CRDs archive verification to enhance file system robustness
Signed-off-by: zhzhuang-zju <m17799853869@163.com>
This commit is contained in:
parent
ccdf485204
commit
63590cbeee
|
@ -23,6 +23,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
@ -30,6 +31,7 @@ import (
|
||||||
operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
|
operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
|
||||||
"github.com/karmada-io/karmada/operator/pkg/util"
|
"github.com/karmada-io/karmada/operator/pkg/util"
|
||||||
"github.com/karmada-io/karmada/operator/pkg/workflow"
|
"github.com/karmada-io/karmada/operator/pkg/workflow"
|
||||||
|
"github.com/karmada-io/karmada/pkg/util/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -53,6 +55,10 @@ func NewPrepareCrdsTask() workflow.Task {
|
||||||
Name: "Unpack",
|
Name: "Unpack",
|
||||||
Run: runUnpack,
|
Run: runUnpack,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "post-check",
|
||||||
|
Run: postCheck,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,6 +160,9 @@ func runUnpack(r workflow.RunData) error {
|
||||||
exist, _ := util.PathExists(crdsPath)
|
exist, _ := util.PathExists(crdsPath)
|
||||||
if !exist {
|
if !exist {
|
||||||
klog.V(2).InfoS("[runUnpack] CRD yaml files do not exist, unpacking tar file", "unpackDir", crdsDir)
|
klog.V(2).InfoS("[runUnpack] CRD yaml files do not exist, unpacking tar file", "unpackDir", crdsDir)
|
||||||
|
if err = validation.ValidateTarball(crdsTarPath, validation.ValidateCrdsTarBall); err != nil {
|
||||||
|
return fmt.Errorf("[unpack] inValid crd tar, err: %w", err)
|
||||||
|
}
|
||||||
if err := util.Unpack(crdsTarPath, crdsDir); err != nil {
|
if err := util.Unpack(crdsTarPath, crdsDir); err != nil {
|
||||||
return fmt.Errorf("[unpack] failed to unpack crd tar, err: %w", err)
|
return fmt.Errorf("[unpack] failed to unpack crd tar, err: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -165,6 +174,29 @@ func runUnpack(r workflow.RunData) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func postCheck(r workflow.RunData) error {
|
||||||
|
data, ok := r.(InitData)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("post-check task invoked with an invalid data struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
crdsDir, err := getCrdsDir(data)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("[post-check] failed to get CRD dir, err: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, archive := range validation.CrdsArchive {
|
||||||
|
expectedDir := filepath.Join(crdsDir, archive)
|
||||||
|
exist, _ := util.PathExists(expectedDir)
|
||||||
|
if !exist {
|
||||||
|
return fmt.Errorf("[post-check] Lacking the necessary file path: %s", expectedDir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
klog.V(2).InfoS("[post-check] Successfully post-check the crd tar archive", "karmada", klog.KObj(data))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func existCrdsTar(crdsDir string) bool {
|
func existCrdsTar(crdsDir string) bool {
|
||||||
files := util.ListFiles(crdsDir)
|
files := util.ListFiles(crdsDir)
|
||||||
klog.V(2).InfoS("[existCrdsTar] Checking for CRD tar file in directory", "directory", crdsDir)
|
klog.V(2).InfoS("[existCrdsTar] Checking for CRD tar file in directory", "directory", crdsDir)
|
||||||
|
|
|
@ -17,7 +17,13 @@ limitations under the License.
|
||||||
package validation
|
package validation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"compress/gzip"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/go-openapi/jsonpointer"
|
"github.com/go-openapi/jsonpointer"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
@ -372,3 +378,85 @@ func validateOverrideOperator(operator policyv1alpha1.OverriderOperator, value a
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CrdsArchive defines the expected tar archive.
|
||||||
|
var CrdsArchive = []string{"crds", "crds/bases", "crds/patches"}
|
||||||
|
|
||||||
|
// ValidateTarball opens a .tar.gz file, and validates each
|
||||||
|
// entry in the tar archive using a provided validate function.
|
||||||
|
func ValidateTarball(tarball string, validate func(*tar.Header) error) error {
|
||||||
|
r, err := os.Open(tarball)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
gr, err := gzip.NewReader(r)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("new reader failed. %v", err)
|
||||||
|
}
|
||||||
|
defer gr.Close()
|
||||||
|
|
||||||
|
tr := tar.NewReader(gr)
|
||||||
|
for {
|
||||||
|
header, err := tr.Next()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = validate(header); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateCrdsTarBall checks if the CRDs package complies with file specifications.
|
||||||
|
// It verifies the following:
|
||||||
|
// 1. Whether the path is clean.
|
||||||
|
// 2. Whether the file directory structure meets expectations.
|
||||||
|
func ValidateCrdsTarBall(header *tar.Header) error {
|
||||||
|
switch header.Typeflag {
|
||||||
|
case tar.TypeDir:
|
||||||
|
// in Unix-like systems, directory paths in tar archives end with a slash (/) to distinguish them from file paths.
|
||||||
|
if strings.HasSuffix(header.Name, "/") && len(header.Name) > 1 {
|
||||||
|
if !isCleanPath(header.Name[:len(header.Name)-1]) {
|
||||||
|
return fmt.Errorf("the given file contains unclean file dir: %s", header.Name)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !isCleanPath(header.Name) {
|
||||||
|
return fmt.Errorf("the given file contains unclean file dir: %s", header.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !isExpectedPath(header.Name, CrdsArchive) {
|
||||||
|
return fmt.Errorf("the given file contains unexpected file dir: %s", header.Name)
|
||||||
|
}
|
||||||
|
case tar.TypeReg:
|
||||||
|
if !isCleanPath(header.Name) {
|
||||||
|
return fmt.Errorf("the given file contains unclean file path: %s", header.Name)
|
||||||
|
}
|
||||||
|
if !isExpectedPath(header.Name, CrdsArchive) {
|
||||||
|
return fmt.Errorf("the given file contains unexpected file path: %s", header.Name)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unknown type: %v in %s", header.Typeflag, header.Name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isExpectedPath(path string, expectedDirs []string) bool {
|
||||||
|
for _, dir := range expectedDirs {
|
||||||
|
if path == dir || strings.HasPrefix(path, dir+"/") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCleanPath(path string) bool {
|
||||||
|
return path == filepath.Clean(path)
|
||||||
|
}
|
||||||
|
|
|
@ -17,9 +17,12 @@ limitations under the License.
|
||||||
package validation
|
package validation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
corev1 "k8s.io/api/core/v1"
|
corev1 "k8s.io/api/core/v1"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
"k8s.io/utils/ptr"
|
"k8s.io/utils/ptr"
|
||||||
|
@ -717,3 +720,95 @@ func TestValidateApplicationFailover(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCheckOperatorCrdsTar(t *testing.T) {
|
||||||
|
testItems := []struct {
|
||||||
|
name string
|
||||||
|
header *tar.Header
|
||||||
|
expectedErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unclean file dir 'crds/../'",
|
||||||
|
header: &tar.Header{
|
||||||
|
Name: "crds/../",
|
||||||
|
Typeflag: tar.TypeDir,
|
||||||
|
},
|
||||||
|
expectedErr: fmt.Errorf("the given file contains unclean file dir: %s", "crds/../"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unclean file dir 'crds/..'",
|
||||||
|
header: &tar.Header{
|
||||||
|
Name: "crds/..",
|
||||||
|
Typeflag: tar.TypeDir,
|
||||||
|
},
|
||||||
|
expectedErr: fmt.Errorf("the given file contains unclean file dir: %s", "crds/.."),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unexpected file dir '../crds'",
|
||||||
|
header: &tar.Header{
|
||||||
|
Name: "../crds",
|
||||||
|
Typeflag: tar.TypeDir,
|
||||||
|
},
|
||||||
|
expectedErr: fmt.Errorf("the given file contains unexpected file dir: %s", "../crds"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unexpected file dir '..'",
|
||||||
|
header: &tar.Header{
|
||||||
|
Name: "..",
|
||||||
|
Typeflag: tar.TypeDir,
|
||||||
|
},
|
||||||
|
expectedErr: fmt.Errorf("the given file contains unexpected file dir: %s", ".."),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "expected file dir 'crds/'",
|
||||||
|
header: &tar.Header{
|
||||||
|
Name: "crds/",
|
||||||
|
Typeflag: tar.TypeDir,
|
||||||
|
},
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "expected file dir 'crds'",
|
||||||
|
header: &tar.Header{
|
||||||
|
Name: "crds",
|
||||||
|
Typeflag: tar.TypeDir,
|
||||||
|
},
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unclean file path 'crds/../a.yaml'",
|
||||||
|
header: &tar.Header{
|
||||||
|
Name: "crds/../a.yaml",
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
},
|
||||||
|
expectedErr: fmt.Errorf("the given file contains unclean file path: %s", "crds/../a.yaml"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unexpected file path '../crds/a.yaml'",
|
||||||
|
header: &tar.Header{
|
||||||
|
Name: "../crds/a.yaml",
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
},
|
||||||
|
expectedErr: fmt.Errorf("the given file contains unexpected file path: %s", "../crds/a.yaml"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unexpected file path '../a.yaml'",
|
||||||
|
header: &tar.Header{
|
||||||
|
Name: "../a.yaml",
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
},
|
||||||
|
expectedErr: fmt.Errorf("the given file contains unexpected file path: %s", "../a.yaml"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "expected file path 'crds/a.yaml'",
|
||||||
|
header: &tar.Header{
|
||||||
|
Name: "crds/a.yaml",
|
||||||
|
Typeflag: tar.TypeReg,
|
||||||
|
},
|
||||||
|
expectedErr: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, item := range testItems {
|
||||||
|
assert.Equal(t, item.expectedErr, ValidateCrdsTarBall(item.header))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue