Skip to content

Commit

Permalink
add validation for kopia repository server controller secrets (#1940)
Browse files Browse the repository at this point in the history
* add skeleton for validation repoitory server secrets

* fix bugs

* add licence headers

* address review comments

* change the secret type to v1.secrettype

* initialize k8s client instead of all the clients since they are not required

* move the consts to a secrets package so that they can be reused

* move the consts to a secrets package so that they can be reused

* refactor to use common constants

* move the consts to a secrets package so that they can be reused

* refactor to use the constants from secrets folder

* address review comments

* add headers

* address review comments

* move location key constants to secrets package

* move location key constants to repositoryserver package

* refactor use the constants from repositoryserver package

* resolve merge conflicts

* address review comments

* add support for filestore secret

* make lint happy

* address comments

* make lint happy

* add licence headers

* add comments on the constants

* consistent error messages

* Change name of constants for better readability

* fix typo

* simplify validation logic

* change error message

* remove unwanted comments

* add comment in the validate repository password function

* change function name for getLocationType to getLocationSecret

* add nil checks for secrets

* fix build issues

* add empty secret checks

* rename interface name to secret for readability

* address review comments

* address review comments

* use cmd.Context
  • Loading branch information
kale-amruta authored Jun 20, 2023
1 parent ac3723d commit 7c81e70
Show file tree
Hide file tree
Showing 22 changed files with 694 additions and 27 deletions.
7 changes: 2 additions & 5 deletions pkg/controllers/repositoryserver/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,7 @@ import (

"github.com/kanisterio/kanister/pkg/kopia/command"
"github.com/kanisterio/kanister/pkg/kopia/repository"
)

const (
repoPasswordKey = "repo-password"
reposerver "github.com/kanisterio/kanister/pkg/secrets/repositoryserver"
)

func (h *RepoServerHandler) connectToKopiaRepository() error {
Expand All @@ -32,7 +29,7 @@ func (h *RepoServerHandler) connectToKopiaRepository() error {
}
args := command.RepositoryCommandArgs{
CommandArgs: &command.CommandArgs{
RepoPassword: string(h.RepositoryServerSecrets.repositoryPassword.Data[repoPasswordKey]),
RepoPassword: string(h.RepositoryServerSecrets.repositoryPassword.Data[reposerver.RepoPasswordKey]),
ConfigFilePath: command.DefaultConfigFilePath,
LogDirectory: command.DefaultLogDirectory,
},
Expand Down
11 changes: 5 additions & 6 deletions pkg/controllers/repositoryserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@ import (
"github.com/kanisterio/kanister/pkg/kopia/command"
"github.com/kanisterio/kanister/pkg/kopia/maintenance"
"github.com/kanisterio/kanister/pkg/kube"
reposerver "github.com/kanisterio/kanister/pkg/secrets/repositoryserver"
"github.com/pkg/errors"
"sigs.k8s.io/kustomize/kyaml/sets"
)

const (
serverAdminUserNameKey = "username"
serverAdminPasswordKey = "password"
// DefaultServerStartTimeout is default time to create context for Kopia API server Status Command
DefaultServerStartTimeout = 600 * time.Second
)
Expand Down Expand Up @@ -87,10 +86,10 @@ func (h *RepoServerHandler) getServerDetails(ctx context.Context) (string, strin
}
var serverAdminUsername, serverAdminPassword []byte
var ok bool
if serverAdminUsername, ok = h.RepositoryServerSecrets.serverAdmin.Data[serverAdminUserNameKey]; !ok {
if serverAdminUsername, ok = h.RepositoryServerSecrets.serverAdmin.Data[reposerver.AdminUsernameKey]; !ok {
return "", "", "", errors.New("Server admin username is not specified")
}
if serverAdminPassword, ok = h.RepositoryServerSecrets.serverAdmin.Data[serverAdminPasswordKey]; !ok {
if serverAdminPassword, ok = h.RepositoryServerSecrets.serverAdmin.Data[reposerver.AdminPasswordKey]; !ok {
return "", "", "", errors.New("Server admin password is not specified")
}
return repoServerAddress, string(serverAdminUsername), string(serverAdminPassword), nil
Expand Down Expand Up @@ -119,7 +118,7 @@ func (h *RepoServerHandler) waitForServerReady(ctx context.Context, serverAddres
}

func (h *RepoServerHandler) createOrUpdateClientUsers(ctx context.Context) error {
repoPassword := string(h.RepositoryServerSecrets.repositoryPassword.Data[repoPasswordKey])
repoPassword := string(h.RepositoryServerSecrets.repositoryPassword.Data[reposerver.RepoPasswordKey])

cmd := command.ServerListUser(
command.ServerListUserCommmandArgs{
Expand Down Expand Up @@ -204,7 +203,7 @@ func (h *RepoServerHandler) createOrUpdateClientUsers(ctx context.Context) error
}

func (h *RepoServerHandler) refreshServer(ctx context.Context, serverAddress, username, password string) error {
repoPassword := string(h.RepositoryServerSecrets.repositoryPassword.Data[repoPasswordKey])
repoPassword := string(h.RepositoryServerSecrets.repositoryPassword.Data[reposerver.RepoPasswordKey])
fingerprint, err := kopia.ExtractFingerprintFromCertSecret(ctx, h.KubeCli, h.RepositoryServerSecrets.serverTLS.Name, h.RepositoryServer.Namespace)
if err != nil {
return errors.Wrap(err, "Failed to extract fingerprint from Kopia API server certificate secret data")
Expand Down
74 changes: 74 additions & 0 deletions pkg/kanctl/secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2023 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 kanctl

import (
"context"
"os"

"github.com/pkg/errors"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8sYAML "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/kubernetes"

"github.com/kanisterio/kanister/pkg/kube"
"github.com/kanisterio/kanister/pkg/secrets"
)

func performRepoServerSecretsValidation(ctx context.Context, p *validateParams) error {
var cli kubernetes.Interface
var secret *corev1.Secret

cli, err := kube.NewClient()
if err != nil {
return errors.Wrap(err, "could not get the kubernetes client")
}

secret, err = getSecretFromCmd(ctx, cli, p)
if err != nil {
return err
}
return secrets.ValidateRepositoryServerSecret(secret)
}

func getSecretFromCmd(ctx context.Context, cli kubernetes.Interface, p *validateParams) (*corev1.Secret, error) {
if p.name != "" {
return cli.CoreV1().Secrets(p.namespace).Get(ctx, p.name, metav1.GetOptions{})
}
return getSecretFromFile(ctx, p.filename)
}

func getSecretFromFile(ctx context.Context, filename string) (*corev1.Secret, error) {
var f *os.File
var err error

if filename == "-" {
f = os.Stdin
} else {
f, err = os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
}
d := k8sYAML.NewYAMLOrJSONDecoder(f, 4096)
secret := &corev1.Secret{}
err = d.Decode(secret)
if err != nil {
return nil, errors.Wrap(err, "failed to decode the secret passed")
}
return secret, nil
}
3 changes: 2 additions & 1 deletion pkg/kanctl/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@ func performValidation(cmd *cobra.Command, args []string) error {
return err
}
cmd.SilenceUsage = true

switch p.resourceKind {
case "profile":
return performProfileValidation(p)
case "blueprint":
return performBlueprintValidation(p)
case "repository-server-secrets":
return performRepoServerSecretsValidation(cmd.Context(), p)
default:
return errors.Errorf("resource %s is not supported for validate subcommand", p.resourceKind)
}
Expand Down
19 changes: 17 additions & 2 deletions pkg/secrets/aws.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
// Copyright 2023 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 secrets

import (
"context"
"time"

"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"

"github.com/kanisterio/kanister/pkg/aws"
"github.com/kanisterio/kanister/pkg/field"
"github.com/kanisterio/kanister/pkg/log"
"github.com/pkg/errors"
secerrors "github.com/kanisterio/kanister/pkg/secrets/errors"
)

const (
Expand Down Expand Up @@ -40,7 +55,7 @@ const (
// - session_token
func ValidateAWSCredentials(secret *v1.Secret) error {
if string(secret.Type) != AWSSecretType {
return errors.New("Secret is not AWS secret")
return errors.Wrapf(secerrors.ErrValidate, secerrors.IncompatibleSecretTypeErrorMsg, AWSSecretType, secret.Namespace, secret.Name)
}
count := 0
if _, ok := secret.Data[AWSAccessKeyID]; ok {
Expand Down
14 changes: 14 additions & 0 deletions pkg/secrets/aws_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2023 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 secrets

import (
Expand Down
21 changes: 19 additions & 2 deletions pkg/secrets/azure.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
// Copyright 2023 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 secrets

import (
"github.com/kanisterio/kanister/pkg/objectstore"
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"

"github.com/kanisterio/kanister/pkg/objectstore"
secerrors "github.com/kanisterio/kanister/pkg/secrets/errors"
)

const (
// AzureSecretType represents the secret type for Azure credentials.
AzureSecretType string = "secrets.kanister.io/azure"

// AzureStorageAccountID is the config map key for Azure storage account id data
Expand All @@ -29,7 +46,7 @@ const (
// - azure_storage_environment
func ValidateAzureCredentials(secret *v1.Secret) error {
if string(secret.Type) != AzureSecretType {
return errors.New("Secret is not Azure secret")
return errors.Wrapf(secerrors.ErrValidate, secerrors.IncompatibleSecretTypeErrorMsg, AzureSecretType, secret.Namespace, secret.Name)
}
count := 0
if _, ok := secret.Data[AzureStorageAccountID]; ok {
Expand Down
14 changes: 14 additions & 0 deletions pkg/secrets/azure_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
// Copyright 2023 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 secrets

import (
Expand Down
35 changes: 35 additions & 0 deletions pkg/secrets/errors/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2023 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 errors

import "fmt"

var ErrValidate = fmt.Errorf("validation Failed")

const (
// Error msg for missing required field in the secret
MissingRequiredFieldErrorMsg = "Missing required field %s in the secret '%s:%s'"
// Error msg for unknown in the secret
UnknownFieldErrorMsg = "'%s:%s' secret has an unknown field"
// Unsupported location type in the secret
UnsupportedLocationTypeErrorMsg = "Unsupported location type '%s' for secret '%s:%s'"
// Invalid Secret type error msg
IncompatibleSecretTypeErrorMsg = "Incompatible secret type. Expected type %s in the secret '%s:%s'"

// Nil Secret error message
NilSecretErrorMessage = "Secret is Nil"
// Empty Secret error message
EmptySecretErrorMessage = "Empty secret. Expected at least one key value pair in the secret '%s:%s'"
)
18 changes: 18 additions & 0 deletions pkg/secrets/filestore.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2023 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 secrets

// FilestoreSecretType represents the secret type for Filestore credentials.
const FilestoreSecretType string = "secrets.kanister.io/filestore"
50 changes: 50 additions & 0 deletions pkg/secrets/gcp.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,58 @@
// Copyright 2023 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 secrets

import (
"github.com/pkg/errors"
v1 "k8s.io/api/core/v1"

secerrors "github.com/kanisterio/kanister/pkg/secrets/errors"
)

const (
// GCPProjectID is the config map key for gcp project id data
GCPProjectID string = "gcp_project_id"
// GCPServiceKey is the config map key for gcp service key data
GCPServiceKey string = "gcp_service_key"
// GCPServerAccountJsonKey is the key for gcp service account json
GCPServiceAccountJsonKey string = "service-account.json"

// GCPSecretType represents the secret type for GCP credentials.
GCPSecretType string = "secrets.kanister.io/gcp"
)

// ValidateGCPCredentials function is to verify the schema of GCP secrets
// that need to be provided for kopia commands
func ValidateGCPCredentials(secret *v1.Secret) error {
// Required fields for the secret are
// - GCPProjectID
// - GCPServiceAccountJsonKey
if secret == nil {
return errors.Wrapf(secerrors.ErrValidate, secerrors.NilSecretErrorMessage)
}
if string(secret.Type) != GCPSecretType {
return errors.Wrapf(secerrors.ErrValidate, secerrors.IncompatibleSecretTypeErrorMsg, GCPSecretType, secret.Namespace, secret.Name)
}
if len(secret.Data) == 0 {
return errors.Wrapf(secerrors.ErrValidate, secerrors.EmptySecretErrorMessage, secret.Namespace, secret.Name)
}
if _, ok := secret.Data[GCPProjectID]; !ok {
return errors.Wrapf(secerrors.ErrValidate, secerrors.MissingRequiredFieldErrorMsg, GCPProjectID, secret.Namespace, secret.Name)
}
if _, ok := secret.Data[GCPServiceAccountJsonKey]; !ok {
return errors.Wrapf(secerrors.ErrValidate, secerrors.MissingRequiredFieldErrorMsg, GCPServiceAccountJsonKey, secret.Namespace, secret.Name)
}
return nil
}
Loading

0 comments on commit 7c81e70

Please sign in to comment.