Merge pull request #1059 from stevvooe/namespaces-charset
namespaces: enforce a character set for namespaces
This commit is contained in:
commit
98e17a2b97
@ -21,6 +21,10 @@ func (s *namespaceStore) Create(ctx context.Context, namespace string, labels ma
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := namespaces.Validate(namespace); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// provides the already exists error.
|
// provides the already exists error.
|
||||||
bkt, err := topbkt.CreateBucket([]byte(namespace))
|
bkt, err := topbkt.CreateBucket([]byte(namespace))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -37,7 +37,9 @@ func NamespaceFromEnv(ctx context.Context) context.Context {
|
|||||||
return WithNamespace(ctx, namespace)
|
return WithNamespace(ctx, namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Namespace returns the namespace from the context
|
// Namespace returns the namespace from the context.
|
||||||
|
//
|
||||||
|
// The namespace is not guaranteed to be valid.
|
||||||
func Namespace(ctx context.Context) (string, bool) {
|
func Namespace(ctx context.Context) (string, bool) {
|
||||||
namespace, ok := ctx.Value(namespaceKey{}).(string)
|
namespace, ok := ctx.Value(namespaceKey{}).(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -52,12 +54,16 @@ func IsNamespaceRequired(err error) bool {
|
|||||||
return errors.Cause(err) == errNamespaceRequired
|
return errors.Cause(err) == errNamespaceRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
// NamespaceRequired returns the namespace or an error
|
// NamespaceRequired returns the valid namepace from the context or an error.
|
||||||
func NamespaceRequired(ctx context.Context) (string, error) {
|
func NamespaceRequired(ctx context.Context) (string, error) {
|
||||||
namespace, ok := Namespace(ctx)
|
namespace, ok := Namespace(ctx)
|
||||||
if !ok || namespace == "" {
|
if !ok || namespace == "" {
|
||||||
return "", errNamespaceRequired
|
return "", errNamespaceRequired
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := Validate(namespace); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
return namespace, nil
|
return namespace, nil
|
||||||
}
|
}
|
||||||
|
52
namespaces/validate.go
Normal file
52
namespaces/validate.go
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
package namespaces
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
label = `[a-z][a-z0-9]+(?:[-]+[a-z0-9]+)*`
|
||||||
|
)
|
||||||
|
|
||||||
|
func reGroup(s string) string {
|
||||||
|
return `(?:` + s + `)`
|
||||||
|
}
|
||||||
|
|
||||||
|
func reAnchor(s string) string {
|
||||||
|
return `^` + s + `$`
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// namespaceRe validates that a namespace matches valid namespaces.
|
||||||
|
//
|
||||||
|
// Rules for domains, defined in RFC 1035, section 2.3.1, are used for
|
||||||
|
// namespaces.
|
||||||
|
namespaceRe = regexp.MustCompile(reAnchor(label + reGroup("[.]"+reGroup(label)) + "*"))
|
||||||
|
|
||||||
|
errNamespaceInvalid = errors.Errorf("invalid namespace, must match %v", namespaceRe)
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsNamespacesValid return true if the error was due to an invalid namespace
|
||||||
|
// name.
|
||||||
|
func IsNamespaceInvalid(err error) bool {
|
||||||
|
return errors.Cause(err) == errNamespaceInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate return nil if the string s is a valid namespace name.
|
||||||
|
//
|
||||||
|
// Namespaces must be valid domain names according to RFC 1035, section 2.3.1.
|
||||||
|
// To enforce case insensitvity, all characters must be lower case.
|
||||||
|
//
|
||||||
|
// In general, namespaces that pass this validation, should be safe for use as
|
||||||
|
// a domain name or filesystem path component.
|
||||||
|
//
|
||||||
|
// Typically, this function is used through NamespacesRequired, rather than
|
||||||
|
// directly.
|
||||||
|
func Validate(s string) error {
|
||||||
|
if !namespaceRe.MatchString(s) {
|
||||||
|
return errors.Wrapf(errNamespaceInvalid, "namespace %q", s)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
97
namespaces/validate_test.go
Normal file
97
namespaces/validate_test.go
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
package namespaces
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidNamespaces(t *testing.T) {
|
||||||
|
for _, testcase := range []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Default",
|
||||||
|
input: "default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Hyphen",
|
||||||
|
input: "default-default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DoubleHyphen",
|
||||||
|
input: "default--default",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "containerD",
|
||||||
|
input: "containerd.io",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SwarmKit",
|
||||||
|
input: "swarmkit.docker.io",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Punycode",
|
||||||
|
input: "zn--e9.org", // or something like it!
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "LeadingPeriod",
|
||||||
|
input: ".foo..foo",
|
||||||
|
err: errNamespaceInvalid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Path",
|
||||||
|
input: "foo/foo",
|
||||||
|
err: errNamespaceInvalid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ParentDir",
|
||||||
|
input: "foo/..",
|
||||||
|
err: errNamespaceInvalid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "RepeatedPeriod",
|
||||||
|
input: "foo..foo",
|
||||||
|
err: errNamespaceInvalid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OutOfPlaceHyphenEmbedded",
|
||||||
|
input: "foo.-boo",
|
||||||
|
err: errNamespaceInvalid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OutOfPlaceHyphen",
|
||||||
|
input: "-foo.boo",
|
||||||
|
err: errNamespaceInvalid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OutOfPlaceHyphenEnd",
|
||||||
|
input: "foo.boo",
|
||||||
|
err: errNamespaceInvalid,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Underscores",
|
||||||
|
input: "foo_foo.boo_underscores", // boo-urns?
|
||||||
|
err: errNamespaceInvalid,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(testcase.name, func(t *testing.T) {
|
||||||
|
if err := Validate(testcase.input); err != nil {
|
||||||
|
if errors.Cause(err) != testcase.err {
|
||||||
|
if testcase.err == nil {
|
||||||
|
t.Fatalf("unexpected error: %v != nil", err)
|
||||||
|
} else {
|
||||||
|
t.Fatalf("expected error %v to be %v", err, testcase.err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Logf("invalid %q detected as invalid: %v", testcase.input, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Logf("%q is a valid namespace", testcase.input)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ import (
|
|||||||
api "github.com/containerd/containerd/api/services/containers"
|
api "github.com/containerd/containerd/api/services/containers"
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/metadata"
|
"github.com/containerd/containerd/metadata"
|
||||||
|
"github.com/containerd/containerd/namespaces"
|
||||||
"github.com/gogo/protobuf/types"
|
"github.com/gogo/protobuf/types"
|
||||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@ -57,6 +58,10 @@ func mapGRPCError(err error, id string) error {
|
|||||||
return grpc.Errorf(codes.NotFound, "container %v not found", id)
|
return grpc.Errorf(codes.NotFound, "container %v not found", id)
|
||||||
case metadata.IsExists(err):
|
case metadata.IsExists(err):
|
||||||
return grpc.Errorf(codes.AlreadyExists, "container %v already exists", id)
|
return grpc.Errorf(codes.AlreadyExists, "container %v already exists", id)
|
||||||
|
case namespaces.IsNamespaceRequired(err):
|
||||||
|
return grpc.Errorf(codes.InvalidArgument, "namespace required, please set %q header", namespaces.GRPCHeader)
|
||||||
|
case namespaces.IsNamespaceInvalid(err):
|
||||||
|
return grpc.Errorf(codes.InvalidArgument, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
@ -85,6 +85,8 @@ func mapGRPCError(err error, id string) error {
|
|||||||
return grpc.Errorf(codes.AlreadyExists, "image %v already exists", id)
|
return grpc.Errorf(codes.AlreadyExists, "image %v already exists", id)
|
||||||
case namespaces.IsNamespaceRequired(err):
|
case namespaces.IsNamespaceRequired(err):
|
||||||
return grpc.Errorf(codes.InvalidArgument, "namespace required, please set %q header", namespaces.GRPCHeader)
|
return grpc.Errorf(codes.InvalidArgument, "namespace required, please set %q header", namespaces.GRPCHeader)
|
||||||
|
case namespaces.IsNamespaceInvalid(err):
|
||||||
|
return grpc.Errorf(codes.InvalidArgument, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
Loading…
Reference in New Issue
Block a user