Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Authorization support #38

Merged
merged 15 commits into from
Mar 29, 2018
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions example/clients-auth-permissions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"users": [
{ "username": "user1", "password": "user1secret" },
{ "username": "user2", "password": "user2secret",
"permissions": {
"publish": ["hello.*"],
"subscribe": ["hello.world"]
}
}
],
"default_permissions": {
"publish": ["SANDBOX.*"],
"subscribe": ["PUBLIC.>"]
}
}
14 changes: 14 additions & 0 deletions example/example-nats-cluster-auth.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
apiVersion: "nats.io/v1alpha2"
kind: "NatsCluster"
metadata:
name: "example-nats-auth"
spec:
size: 3
version: "1.0.4"

auth:
# Definition in JSON of the users permissions
clientsAuthSecret: "nats-clients-auth"

# How long to wait for authentication
clientsAuthTimeout: 5
38 changes: 31 additions & 7 deletions pkg/conf/natsconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,30 +39,54 @@ type TLSConfig struct {
}

type AuthorizationConfig struct {
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Token string `json:"token,omitempty"`
Timeout int `json:"timeout,omitempty"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Token string `json:"token,omitempty"`
Timeout int `json:"timeout,omitempty"`
Users []*User `json:"users,omitempty"`
DefaultPermissions *Permissions `json:"default_permissions,omitempty"`
}

type User struct {
User string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
Permissions *Permissions `json:"permissions,omitempty"`
}

// Permissions are the allowed subjects on a per
// publish or subscribe basis.
type Permissions struct {
Publish []string `json:"publish,omitempty"`
Subscribe []string `json:"subscribe,omitempty"`
}

var (
ErrInvalidConfig = errors.New("natsconf: cannot produce valid config")
)

func Marshal(conf *ServerConfig) ([]byte, error) {
js, err := json.MarshalIndent(conf, "", " ")
buf := &bytes.Buffer{}
encoder := json.NewEncoder(buf)
encoder.SetEscapeHTML(false)
err := encoder.Encode(conf)
Copy link
Member Author

@wallyqs wallyqs Mar 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since gnatsd v1.1.0 is out, could wait for the Docker image to be available and remove all of this and just use JSON here (filed issue to remove: #39)

if err != nil {
return nil, err
}
buf2 := &bytes.Buffer{}
err = json.Indent(buf2, buf.Bytes(), "", " ")
if err != nil {
return nil, err
}
if len(js) < 1 || len(js)-1 <= 1 {
js := buf2.Bytes()
if len(js) < 1 || len(js)-2 <= 1 {
return nil, ErrInvalidConfig
}

// Slice the initial and final brackets from the
// resulting JSON configuration so gnatsd config parsers
// almost treats it as valid config.
js = js[1:]
js = js[:len(js)-1]
js = js[:len(js)-2]

// Replacing all commas with line breaks still keeps
// arrays valid and makes the top level configuration
Expand Down
28 changes: 28 additions & 0 deletions pkg/conf/natsconf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,34 @@ func TestConfMarshal(t *testing.T) {

"key_file": "/etc/nats-tls/server-key.pem"
}
}`,
err: nil,
},
{
input: &ServerConfig{
Port: 4222,
HTTPPort: 8222,
Authorization: &AuthorizationConfig{
DefaultPermissions: &Permissions{
Publish: []string{"PUBLISH.>"},
Subscribe: []string{"PUBLISH.*"},
},
},
},
output: `"port": 4222

"http_port": 8222

"authorization": {
"default_permissions": {
"publish": [
"PUBLISH.>"
]

"subscribe": [
"PUBLISH.*"
]
}
}`,
err: nil,
},
Expand Down
10 changes: 10 additions & 0 deletions pkg/spec/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ type ClusterSpec struct {

// TLS is the configuration to secure the cluster.
TLS *TLSConfig `json:"tls,omitempty"`

// Auth is the configuration to set permissions for users.
Auth *AuthConfig `json:"auth,omitempty"`
}

// TLSConfig is the optional TLS configuration for the cluster.
Expand Down Expand Up @@ -127,6 +130,13 @@ type PodPolicy struct {
NatsEnv []v1.EnvVar `json:"natsEnv,omitempty"`
}

// AuthConfig is the authorization configuration for
// user permissions in the cluster.
type AuthConfig struct {
ClientsAuthSecret string `json:"clientsAuthSecret,omitempty"`
ClientsAuthTimeout int `json:"clientsAuthTimeout,omitempty"`
}

func (c *ClusterSpec) Validate() error {
if c.Pod != nil {
for k := range c.Pod.Labels {
Expand Down
25 changes: 25 additions & 0 deletions pkg/spec/zz_generated.deepcopy.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ import (
runtime "k8s.io/apimachinery/pkg/runtime"
)

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AuthConfig) DeepCopyInto(out *AuthConfig) {
*out = *in
return
}

// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AuthConfig.
func (in *AuthConfig) DeepCopy() *AuthConfig {
if in == nil {
return nil
}
out := new(AuthConfig)
in.DeepCopyInto(out)
return out
}

// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ClusterCondition) DeepCopyInto(out *ClusterCondition) {
*out = *in
Expand Down Expand Up @@ -60,6 +76,15 @@ func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) {
**out = **in
}
}
if in.Auth != nil {
in, out := &in.Auth, &out.Auth
if *in == nil {
*out = nil
} else {
*out = new(AuthConfig)
**out = **in
}
}
return
}

Expand Down
36 changes: 36 additions & 0 deletions pkg/util/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,34 @@ func addTLSConfig(sconfig *natsconf.ServerConfig, cs spec.ClusterSpec) {
}
}

// addAuthConfig fills the Auth configuration to be used in config map.
func addAuthConfig(kubecli corev1client.CoreV1Interface, ns string, sconfig *natsconf.ServerConfig, cs spec.ClusterSpec) error {
if cs.Auth == nil {
return nil
}
if cs.Auth.ClientsAuthSecret != "" {
result, err := kubecli.Secrets(ns).Get(cs.Auth.ClientsAuthSecret, metav1.GetOptions{})
if err != nil {
return err
}

var clientAuth *natsconf.AuthorizationConfig
for _, v := range result.Data {
err := json.Unmarshal(v, &clientAuth)
if err != nil {
return err
}
if cs.Auth.ClientsAuthTimeout > 0 {
clientAuth.Timeout = cs.Auth.ClientsAuthTimeout
}
sconfig.Authorization = clientAuth
break
}
return nil
}
return nil
}

// CreateAndWaitPod is an util for testing.
// We should eventually get rid of this in critical code path and move it to test util.
func CreateAndWaitPod(kubecli corev1client.CoreV1Interface, ns string, pod *v1.Pod, timeout time.Duration) (*v1.Pod, error) {
Expand Down Expand Up @@ -191,6 +219,10 @@ func CreateConfigMap(kubecli corev1client.CoreV1Interface, clusterName, ns strin
},
}
addTLSConfig(sconfig, cluster)
err := addAuthConfig(kubecli, ns, sconfig, cluster)
if err != nil {
return err
}

rawConfig, err := natsconf.Marshal(sconfig)
if err != nil {
Expand Down Expand Up @@ -242,6 +274,10 @@ func UpdateConfigMap(kubecli corev1client.CoreV1Interface, clusterName, ns strin
},
}
addTLSConfig(sconfig, cluster)
err = addAuthConfig(kubecli, ns, sconfig, cluster)
if err != nil {
return err
}

rawConfig, err := natsconf.Marshal(sconfig)
if err != nil {
Expand Down