<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)
//
// Tests registered by RegisterFSTests.

package fstesting

import (
	"io/ioutil"
	"math"
	"os"
	"time"

	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcsutil"
	"github.com/jacobsa/gcsfuse/fs"
	"github.com/jacobsa/gcsfuse/fuseutil"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"
)

////////////////////////////////////////////////////////////////////////
// Common
////////////////////////////////////////////////////////////////////////

type fsTest struct {
	ctx    context.Context
	bucket gcs.Bucket
	mfs    *fuseutil.MountedFileSystem
}

var _ fsTestInterface = &fsTest{}

func (t *fsTest) setUpFsTest(b gcs.Bucket) {
	var err error

	// Record bucket and context information.
	t.bucket = b
	t.ctx = context.Background()

	// Set up a temporary directory for mounting.
	mountPoint, err := ioutil.TempDir("", "fs_test")
	if err != nil {
		panic("ioutil.TempDir: " + err.Error())
	}

	// Mount a file system.
	fileSystem, err := fs.NewFuseFS(b)
	if err != nil {
		panic("NewFuseFS: " + err.Error())
	}

	t.mfs = fuseutil.MountFileSystem(mountPoint, fileSystem)
	if err := t.mfs.WaitForReady(t.ctx); err != nil {
		panic("MountedFileSystem.WaitForReady: " + err.Error())
	}
}

func (t *fsTest) tearDownFsTest() {
	// Unmount the file system.
	if err := t.mfs.Unmount(); err != nil {
		panic("MountedFileSystem.Unmount: " + err.Error())
	}

	if err := t.mfs.Join(t.ctx); err != nil {
		panic("MountedFileSystem.Join: " + err.Error())
	}
}

func (t *fsTest) createObjects(objects []*gcsutil.ObjectInfo) error {
	_, err := gcsutil.CreateObjects(t.ctx, t.bucket, objects)
	return err
}

////////////////////////////////////////////////////////////////////////
// Read-only interaction
////////////////////////////////////////////////////////////////////////

type readOnlyTest struct {
	fsTest
}

func (t *readOnlyTest) EmptyRoot() {
	// ReadDir
	entries, err := ioutil.ReadDir(t.mfs.Dir())
	AssertEq(nil, err)

	ExpectThat(entries, ElementsAre())
}

func (t *readOnlyTest) ContentsInRoot() {
	// Set up contents.
	AssertEq(
		nil,
		t.createObjects(
			[]*gcsutil.ObjectInfo{
				// File
				&gcsutil.ObjectInfo{
					Attrs: storage.ObjectAttrs{
						Name: "foo",
					},
					Contents: "taco",
				},

				// Directory
				&gcsutil.ObjectInfo{
					Attrs: storage.ObjectAttrs{
						Name: "bar/",
					},
				},

				// File
				&gcsutil.ObjectInfo{
					Attrs: storage.ObjectAttrs{
						Name: "baz",
					},
					Contents: "burrito",
				},
			}))

	// ReadDir
	entries, err := ioutil.ReadDir(t.mfs.Dir())
	AssertEq(nil, err)

	AssertEq(3, len(entries))
	var e os.FileInfo

	// bar
	e = entries[0]
	ExpectEq("bar", e.Name())
	ExpectEq(0, e.Size())
	ExpectEq(os.ModeDir|os.FileMode(0500), e.Mode())
	ExpectLt(math.Abs(time.Since(e.ModTime()).Seconds()), 30)
	ExpectTrue(e.IsDir())

	// baz
	e = entries[1]
	ExpectEq("baz", e.Name())
	ExpectEq(len("burrito"), e.Size())
	ExpectEq(os.FileMode(0400), e.Mode())
	ExpectLt(math.Abs(time.Since(e.ModTime()).Seconds()), 30)
	ExpectFalse(e.IsDir())

	// foo
	e = entries[2]
	ExpectEq("foo", e.Name())
	ExpectEq(len("taco"), e.Size())
	ExpectEq(os.FileMode(0400), e.Mode())
	ExpectLt(math.Abs(time.Since(e.ModTime()).Seconds()), 30)
	ExpectFalse(e.IsDir())
}

func (t *readOnlyTest) EmptySubDirectory() {
	AssertTrue(false, "TODO")
}

func (t *readOnlyTest) ContentsInSubDirectory() {
	AssertTrue(false, "TODO")
}

func (t *readOnlyTest) ContentsInLeafDirectory() {
	AssertTrue(false, "TODO")
}

// TODO(jacobsa): Inodes
// TODO(jacobsa): Error conditions
func (t *readOnlyTest) DoesFoo() {
	AssertTrue(false, "TODO")
}
<commit_msg>ReadOnlyTest.EmptySubDirectory<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
// Author: jacobsa@google.com (Aaron Jacobs)
//
// Tests registered by RegisterFSTests.

package fstesting

import (
	"io/ioutil"
	"math"
	"os"
	"path"
	"time"

	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcsutil"
	"github.com/jacobsa/gcsfuse/fs"
	"github.com/jacobsa/gcsfuse/fuseutil"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
	"golang.org/x/net/context"
	"google.golang.org/cloud/storage"
)

////////////////////////////////////////////////////////////////////////
// Common
////////////////////////////////////////////////////////////////////////

type fsTest struct {
	ctx    context.Context
	bucket gcs.Bucket
	mfs    *fuseutil.MountedFileSystem
}

var _ fsTestInterface = &fsTest{}

func (t *fsTest) setUpFsTest(b gcs.Bucket) {
	var err error

	// Record bucket and context information.
	t.bucket = b
	t.ctx = context.Background()

	// Set up a temporary directory for mounting.
	mountPoint, err := ioutil.TempDir("", "fs_test")
	if err != nil {
		panic("ioutil.TempDir: " + err.Error())
	}

	// Mount a file system.
	fileSystem, err := fs.NewFuseFS(b)
	if err != nil {
		panic("NewFuseFS: " + err.Error())
	}

	t.mfs = fuseutil.MountFileSystem(mountPoint, fileSystem)
	if err := t.mfs.WaitForReady(t.ctx); err != nil {
		panic("MountedFileSystem.WaitForReady: " + err.Error())
	}
}

func (t *fsTest) tearDownFsTest() {
	// Unmount the file system.
	if err := t.mfs.Unmount(); err != nil {
		panic("MountedFileSystem.Unmount: " + err.Error())
	}

	if err := t.mfs.Join(t.ctx); err != nil {
		panic("MountedFileSystem.Join: " + err.Error())
	}
}

func (t *fsTest) createObjects(objects []*gcsutil.ObjectInfo) error {
	_, err := gcsutil.CreateObjects(t.ctx, t.bucket, objects)
	return err
}

func (t *fsTest) createEmptyObjects(names []string) error {
	_, err := gcsutil.CreateEmptyObjects(t.ctx, t.bucket, names)
	return err
}

////////////////////////////////////////////////////////////////////////
// Read-only interaction
////////////////////////////////////////////////////////////////////////

type readOnlyTest struct {
	fsTest
}

func (t *readOnlyTest) EmptyRoot() {
	// ReadDir
	entries, err := ioutil.ReadDir(t.mfs.Dir())
	AssertEq(nil, err)

	ExpectThat(entries, ElementsAre())
}

func (t *readOnlyTest) ContentsInRoot() {
	// Set up contents.
	AssertEq(
		nil,
		t.createObjects(
			[]*gcsutil.ObjectInfo{
				// File
				&gcsutil.ObjectInfo{
					Attrs: storage.ObjectAttrs{
						Name: "foo",
					},
					Contents: "taco",
				},

				// Directory
				&gcsutil.ObjectInfo{
					Attrs: storage.ObjectAttrs{
						Name: "bar/",
					},
				},

				// File
				&gcsutil.ObjectInfo{
					Attrs: storage.ObjectAttrs{
						Name: "baz",
					},
					Contents: "burrito",
				},
			}))

	// ReadDir
	entries, err := ioutil.ReadDir(t.mfs.Dir())
	AssertEq(nil, err)

	AssertEq(3, len(entries))
	var e os.FileInfo

	// bar
	e = entries[0]
	ExpectEq("bar", e.Name())
	ExpectEq(0, e.Size())
	ExpectEq(os.ModeDir|os.FileMode(0500), e.Mode())
	ExpectLt(math.Abs(time.Since(e.ModTime()).Seconds()), 30)
	ExpectTrue(e.IsDir())

	// baz
	e = entries[1]
	ExpectEq("baz", e.Name())
	ExpectEq(len("burrito"), e.Size())
	ExpectEq(os.FileMode(0400), e.Mode())
	ExpectLt(math.Abs(time.Since(e.ModTime()).Seconds()), 30)
	ExpectFalse(e.IsDir())

	// foo
	e = entries[2]
	ExpectEq("foo", e.Name())
	ExpectEq(len("taco"), e.Size())
	ExpectEq(os.FileMode(0400), e.Mode())
	ExpectLt(math.Abs(time.Since(e.ModTime()).Seconds()), 30)
	ExpectFalse(e.IsDir())
}

func (t *readOnlyTest) EmptySubDirectory() {
	// Set up an empty directory placeholder called 'bar'.
	AssertEq(nil, t.createEmptyObjects([]string{"bar/"}))

	// ReadDir
	entries, err := ioutil.ReadDir(path.Join(t.mfs.Dir(), "bar"))
	AssertEq(nil, err)

	ExpectThat(entries, ElementsAre())
}

func (t *readOnlyTest) ContentsInSubDirectory() {
	AssertTrue(false, "TODO")
}

func (t *readOnlyTest) ContentsInLeafDirectory() {
	AssertTrue(false, "TODO")
}

// TODO(jacobsa): Inodes
// TODO(jacobsa): Error conditions
func (t *readOnlyTest) DoesFoo() {
	AssertTrue(false, "TODO")
}
<|endoftext|>


<commit_before>/*
Tiny web server to handle slack outgoing webhook and push the data to resque
*/
package main

import (
	"fmt"
	"flag"
	"net/http"
	"net/url"
	"encoding/json"
	"strings"
	"github.com/fzzy/radix/redis"
	"github.com/glacjay/goini"
)

// dictionary of string
type Dict map[string]bool
func StringToDict(s string, d Dict) {
	for _, v := range strings.Split(s, ",") {
		d[v] = true
	}
	return
}

// access rule
type AccessRule struct {
	Name string
	Users Dict
	Policy bool
	Class string
	Queue string
	Allow_msg string
	Deny_msg string
}

type AccessList map[string]*AccessRule

// some default global variable
var (
	PORT string = ":8765"
	API_PATH string = "/api"
	REDIS string = "localhost:6379"
	CLASS string = "SlackOPS"
	QUEUE string = "slackops"
	TOKENS Dict = make(Dict)
	VERBOSE bool = false
	FLAGS Dict = make(Dict)
	ACCESS_LIST AccessList = make(AccessList)
	ALLOW_MSG = "please wait"
	DENY_MSG = "task denied"
	UNDEFINED_JOB_CAN_PASS = true
	err error
)

func check_flag(f *flag.Flag) {
	FLAGS[f.Name] = true
}

func main() {
	// config parameter
	p_port := flag.String("p", "8765", "listen port")
	p_redis := flag.String("r", "localhost:6379", "redis host")
	p_class := flag.String("c", "SlackOPS", "resque class")
	p_queue := flag.String("q", "slackops", "resque queue")
	p_tokens := flag.String("t", "", "slack tokens, split by a comma (,)")
	p_verbose := flag.Bool("v", false, "verbose")
	p_config := flag.String("C", "", "configuration file")
	p_undefined_job_can_pass := flag.Bool("undefined-job-can-pass", false, "Undefined job can pass or not")
	p_allow_msg := flag.String("allow-msg", "please wait", "Specify default allow response text")
	p_deny_msg := flag.String("deny-msg", "task denied", "Specify default deny response text")
	flag.Parse()

	// mark parsed flags
	flag.Visit(check_flag)

	// read config file
	if FLAGS["C"] {
		config, err := ini.Load(*p_config)
		if err != nil {
			fmt.Println("error:", err)
			return
		}

		// load general config
		c_port, exist := config.GetString("general", "port")
		if exist {
			PORT = ":" + c_port
		}
		c_redis, exist := config.GetString("general", "redis")
		if exist {
			REDIS = c_redis
		}
		c_class, exist := config.GetString("general", "class")
		if exist {
			CLASS = c_class
		}
		c_queue, exist := config.GetString("general", "queue")
		if exist {
			QUEUE = c_queue
		}
		c_tokens, exist := config.GetString("general", "tokens")
		if exist {
			StringToDict(c_tokens, TOKENS)
		}
		c_verbose, exist := config.GetBool("general", "verbose")
		if exist {
			VERBOSE = c_verbose
		}
		c_undefined_job_can_pass, exist := config.GetBool("general", "undefined_job_can_pass")
		if exist {
			UNDEFINED_JOB_CAN_PASS = c_undefined_job_can_pass
		}
		c_allow_msg, exist := config.GetString("general", "allow_msg")
		if exist {
			ALLOW_MSG = c_allow_msg
		}
		c_deny_msg, exist := config.GetString("general", "deny_msg")
		if exist {
			DENY_MSG = c_deny_msg
		}

		// load access list
		for _, s := range config.GetSections() {
			if strings.HasPrefix(s, "job: ") {
				// read users and policy (mandatory)
				users_string, e_users := config.GetString(s, "users")
				policy_string, e_policy := config.GetString(s, "policy")
				if !(e_users && e_policy) {
					fmt.Printf("Skipping %s\nError: policy and users are mandatory in a job", s)
					continue
				}

				// read optional config
				allow_msg, _ := config.GetString(s, "allow_msg")
				deny_msg, _ := config.GetString(s, "deny_msg")
				class, _ := config.GetString(s, "class")
				queue, _ := config.GetString(s, "queue")

				// parse users and policy
				var policy bool
				if policy_string == "allow" {
					policy = true
				} else if policy_string == "deny" {
					policy = false
				} else {
					fmt.Printf("Skipping %s\nError: policy must be either 'allow' or 'deny'", s)
					continue
				}
				users := make(Dict)
				StringToDict(users_string, users)

				// create AccessRule
				ar := AccessRule{
					Name: strings.TrimPrefix(s, "job: "),
					Users: users,
					Policy: policy,
					Class: class,
					Queue: queue,
					Allow_msg: allow_msg,
					Deny_msg: deny_msg,
				}
				ACCESS_LIST[ar.Name] = &ar
			}
		}
	}

	// override configuration with flags
	if FLAGS["p"] {
		PORT = ":" + *p_port
	}
	if FLAGS["r"] {
		REDIS = *p_redis
	}
	if FLAGS["c"] {
		CLASS = *p_class
	}
	if FLAGS["q"] {
		QUEUE = *p_queue
	}
	if FLAGS["t"] {
		StringToDict(*p_tokens, TOKENS)
	}
	if FLAGS["v"] {
		VERBOSE = *p_verbose
	}
	if FLAGS["undefined-job-can-pass"] {
		UNDEFINED_JOB_CAN_PASS = *p_undefined_job_can_pass
	}
	if FLAGS["allow-msg"] {
		ALLOW_MSG = *p_allow_msg
	}
	if FLAGS["deny-msg"] {
		DENY_MSG = *p_deny_msg
	}

	if VERBOSE {
		fmt.Printf("Listening on port %s using resque at %s\n", PORT, REDIS)
		fmt.Printf("Accepting tokens:\n")
		for t, _ := range TOKENS {
			fmt.Printf("+ %s\n", t)
		}
		fmt.Printf("Jobs will be pushed to queue %s, class %s\n", QUEUE, CLASS)
	}

	// connect to redis and add queue
	rcon, err := redis.Dial("tcp", REDIS)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	rcon.Cmd("SADD", "resque:queues", QUEUE)
	// add queue optionally specified in jobs
	for k, v := range ACCESS_LIST {
		if len(v.Queue) > 0 {
			rcon.Cmd("SADD", "resque:queues", v.Queue)
			if VERBOSE {
				fmt.Printf("Job '%s' will be pushed to queue %s", k, v.Queue)
				if len(v.Class) > 0 {
					fmt.Printf(", class %s", v.Class)
				}
				fmt.Printf("\n")
			}
		}
	}
	rcon.Close()

	// start web app
	http.HandleFunc(API_PATH, apiHandler)
	err = http.ListenAndServe(PORT, nil)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
	type Response struct {
		Text string `json:"text"`
	}

	type Job struct {
		Class string `json:"class"`
		Args []string `json:"args"`
	}

	// accept only POST
	if r.Method != "POST" {
		return
	}

	// read and parse request body content
	body := make([]byte, r.ContentLength)
	r.Body.Read(body)
	data, err := url.ParseQuery(string(body))
	if err != nil {
		fmt.Println("error:", err)
	}

	// check token
	if ! TOKENS[data.Get("token")] {
		return
	}

	// get request and user name
	request := strings.TrimPrefix(data.Get("text"), data.Get("trigger_word"))
	request = strings.TrimLeft(request, " ")
	request = strings.TrimRight(request, " ")
	user := data.Get("user_name")

	// filter with access list
	var pass bool = UNDEFINED_JOB_CAN_PASS
	var response_text string
	if UNDEFINED_JOB_CAN_PASS {
		response_text = ALLOW_MSG
	} else {
		response_text = DENY_MSG
	}
	var class string = CLASS
	var queue string = "resque:queue:" + QUEUE

	// check if job is defined
	job_is_defined := false
	var job_type string
	for k, v := range ACCESS_LIST {
		if strings.HasPrefix(request, k) {
			if v != nil {
				job_is_defined = true
				job_type = k
				break
			}
		}
	}

	// if job is defined
	if job_is_defined {
		// if user in deny list
		if (!ACCESS_LIST[job_type].Policy && ACCESS_LIST[job_type].Users[user]) {
			pass = false

		// if user not in allow list
		} else if (ACCESS_LIST[job_type].Policy && !ACCESS_LIST[job_type].Users[user]) {
			pass = false

		// if user in allow list
		} else if (ACCESS_LIST[job_type].Policy && ACCESS_LIST[job_type].Users[user]) {
			pass = true

		// if user not in deny list
		} else if (!ACCESS_LIST[job_type].Policy && !ACCESS_LIST[job_type].Users[user]) {
			pass = true
		}

		// if user is allowed
		if pass {
			// choose response text
			if len(ACCESS_LIST[job_type].Allow_msg) > 0 {
				response_text = ACCESS_LIST[job_type].Allow_msg
			} else {
				response_text = ALLOW_MSG
			}

			// choose class
			if len(ACCESS_LIST[job_type].Class) > 0 {
				class = ACCESS_LIST[job_type].Class
			} else {
				class = CLASS
			}

			// choose queue
			if len(ACCESS_LIST[job_type].Queue) > 0 {
				queue = "resque:queue:" + ACCESS_LIST[job_type].Queue
			} else {
				queue = "resque:queue:" + QUEUE
			}

		// if user is denied
		} else {
			if len(ACCESS_LIST[job_type].Deny_msg) > 0 {
				response_text = ACCESS_LIST[job_type].Deny_msg
			} else {
				response_text = DENY_MSG
			}
		}

	} else {
		// undefined job
		if pass {
			response_text = ALLOW_MSG
			class = CLASS
			queue = "resque:queue:" + QUEUE
		} else {
			response_text = DENY_MSG
		}
	}

	if pass {
		// make connection to redis
		rcon, err := redis.Dial("tcp", REDIS)
		if err != nil {
			fmt.Println("error:", err)
		}

		// create job
		job := Job{
			Class: class,
			Args: []string {
			"request=" + request,
			"user=" + user,
			"channel_name=" + data.Get("channel_name"),
			"timestamp=" + data.Get("timestamp"),
			},
		}
		json_job, err := json.Marshal(job)
		if err != nil {
			fmt.Println("error:", err)
		}
		// push job to resque
		rcon.Cmd("RPUSH", queue, string(json_job))
		rcon.Close()
	}

	// response to slack
	res := Response{
		Text: "@" + user + ": " + response_text,
	}
	json_res, err := json.Marshal(res)
	if err != nil {
		fmt.Println("error:", err)
	}
	w.Header().Set("Content-type", "application/json")
	fmt.Fprintf(w, string(json_res))
}
<commit_msg>reformat using gofmt<commit_after>/*
Tiny web server to handle slack outgoing webhook and push the data to resque
*/
package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"github.com/fzzy/radix/redis"
	"github.com/glacjay/goini"
	"net/http"
	"net/url"
	"strings"
)

// dictionary of string
type Dict map[string]bool

func StringToDict(s string, d Dict) {
	for _, v := range strings.Split(s, ",") {
		d[v] = true
	}
	return
}

// access rule
type AccessRule struct {
	Name      string
	Users     Dict
	Policy    bool
	Class     string
	Queue     string
	Allow_msg string
	Deny_msg  string
}

type AccessList map[string]*AccessRule

// some default global variable
var (
	PORT                   string     = ":8765"
	API_PATH               string     = "/api"
	REDIS                  string     = "localhost:6379"
	CLASS                  string     = "SlackOPS"
	QUEUE                  string     = "slackops"
	TOKENS                 Dict       = make(Dict)
	VERBOSE                bool       = false
	FLAGS                  Dict       = make(Dict)
	ACCESS_LIST            AccessList = make(AccessList)
	ALLOW_MSG                         = "please wait"
	DENY_MSG                          = "task denied"
	UNDEFINED_JOB_CAN_PASS            = true
	err                    error
)

func check_flag(f *flag.Flag) {
	FLAGS[f.Name] = true
}

func main() {
	// config parameter
	p_port := flag.String("p", "8765", "listen port")
	p_redis := flag.String("r", "localhost:6379", "redis host")
	p_class := flag.String("c", "SlackOPS", "resque class")
	p_queue := flag.String("q", "slackops", "resque queue")
	p_tokens := flag.String("t", "", "slack tokens, split by a comma (,)")
	p_verbose := flag.Bool("v", false, "verbose")
	p_config := flag.String("C", "", "configuration file")
	p_undefined_job_can_pass := flag.Bool("undefined-job-can-pass", false, "Undefined job can pass or not")
	p_allow_msg := flag.String("allow-msg", "please wait", "Specify default allow response text")
	p_deny_msg := flag.String("deny-msg", "task denied", "Specify default deny response text")
	flag.Parse()

	// mark parsed flags
	flag.Visit(check_flag)

	// read config file
	if FLAGS["C"] {
		config, err := ini.Load(*p_config)
		if err != nil {
			fmt.Println("error:", err)
			return
		}

		// load general config
		c_port, exist := config.GetString("general", "port")
		if exist {
			PORT = ":" + c_port
		}
		c_redis, exist := config.GetString("general", "redis")
		if exist {
			REDIS = c_redis
		}
		c_class, exist := config.GetString("general", "class")
		if exist {
			CLASS = c_class
		}
		c_queue, exist := config.GetString("general", "queue")
		if exist {
			QUEUE = c_queue
		}
		c_tokens, exist := config.GetString("general", "tokens")
		if exist {
			StringToDict(c_tokens, TOKENS)
		}
		c_verbose, exist := config.GetBool("general", "verbose")
		if exist {
			VERBOSE = c_verbose
		}
		c_undefined_job_can_pass, exist := config.GetBool("general", "undefined_job_can_pass")
		if exist {
			UNDEFINED_JOB_CAN_PASS = c_undefined_job_can_pass
		}
		c_allow_msg, exist := config.GetString("general", "allow_msg")
		if exist {
			ALLOW_MSG = c_allow_msg
		}
		c_deny_msg, exist := config.GetString("general", "deny_msg")
		if exist {
			DENY_MSG = c_deny_msg
		}

		// load access list
		for _, s := range config.GetSections() {
			if strings.HasPrefix(s, "job: ") {
				// read users and policy (mandatory)
				users_string, e_users := config.GetString(s, "users")
				policy_string, e_policy := config.GetString(s, "policy")
				if !(e_users && e_policy) {
					fmt.Printf("Skipping %s\nError: policy and users are mandatory in a job", s)
					continue
				}

				// read optional config
				allow_msg, _ := config.GetString(s, "allow_msg")
				deny_msg, _ := config.GetString(s, "deny_msg")
				class, _ := config.GetString(s, "class")
				queue, _ := config.GetString(s, "queue")

				// parse users and policy
				var policy bool
				if policy_string == "allow" {
					policy = true
				} else if policy_string == "deny" {
					policy = false
				} else {
					fmt.Printf("Skipping %s\nError: policy must be either 'allow' or 'deny'", s)
					continue
				}
				users := make(Dict)
				StringToDict(users_string, users)

				// create AccessRule
				ar := AccessRule{
					Name:      strings.TrimPrefix(s, "job: "),
					Users:     users,
					Policy:    policy,
					Class:     class,
					Queue:     queue,
					Allow_msg: allow_msg,
					Deny_msg:  deny_msg,
				}
				ACCESS_LIST[ar.Name] = &ar
			}
		}
	}

	// override configuration with flags
	if FLAGS["p"] {
		PORT = ":" + *p_port
	}
	if FLAGS["r"] {
		REDIS = *p_redis
	}
	if FLAGS["c"] {
		CLASS = *p_class
	}
	if FLAGS["q"] {
		QUEUE = *p_queue
	}
	if FLAGS["t"] {
		StringToDict(*p_tokens, TOKENS)
	}
	if FLAGS["v"] {
		VERBOSE = *p_verbose
	}
	if FLAGS["undefined-job-can-pass"] {
		UNDEFINED_JOB_CAN_PASS = *p_undefined_job_can_pass
	}
	if FLAGS["allow-msg"] {
		ALLOW_MSG = *p_allow_msg
	}
	if FLAGS["deny-msg"] {
		DENY_MSG = *p_deny_msg
	}

	if VERBOSE {
		fmt.Printf("Listening on port %s using resque at %s\n", PORT, REDIS)
		fmt.Printf("Accepting tokens:\n")
		for t, _ := range TOKENS {
			fmt.Printf("+ %s\n", t)
		}
		fmt.Printf("Jobs will be pushed to queue %s, class %s\n", QUEUE, CLASS)
	}

	// connect to redis and add queue
	rcon, err := redis.Dial("tcp", REDIS)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
	rcon.Cmd("SADD", "resque:queues", QUEUE)
	// add queue optionally specified in jobs
	for k, v := range ACCESS_LIST {
		if len(v.Queue) > 0 {
			rcon.Cmd("SADD", "resque:queues", v.Queue)
			if VERBOSE {
				fmt.Printf("Job '%s' will be pushed to queue %s", k, v.Queue)
				if len(v.Class) > 0 {
					fmt.Printf(", class %s", v.Class)
				}
				fmt.Printf("\n")
			}
		}
	}
	rcon.Close()

	// start web app
	http.HandleFunc(API_PATH, apiHandler)
	err = http.ListenAndServe(PORT, nil)
	if err != nil {
		fmt.Println("error:", err)
		return
	}
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
	type Response struct {
		Text string `json:"text"`
	}

	type Job struct {
		Class string   `json:"class"`
		Args  []string `json:"args"`
	}

	// accept only POST
	if r.Method != "POST" {
		return
	}

	// read and parse request body content
	body := make([]byte, r.ContentLength)
	r.Body.Read(body)
	data, err := url.ParseQuery(string(body))
	if err != nil {
		fmt.Println("error:", err)
	}

	// check token
	if !TOKENS[data.Get("token")] {
		return
	}

	// get request and user name
	request := strings.TrimPrefix(data.Get("text"), data.Get("trigger_word"))
	request = strings.TrimLeft(request, " ")
	request = strings.TrimRight(request, " ")
	user := data.Get("user_name")

	// filter with access list
	var pass bool = UNDEFINED_JOB_CAN_PASS
	var response_text string
	if UNDEFINED_JOB_CAN_PASS {
		response_text = ALLOW_MSG
	} else {
		response_text = DENY_MSG
	}
	var class string = CLASS
	var queue string = "resque:queue:" + QUEUE

	// check if job is defined
	job_is_defined := false
	var job_type string
	for k, v := range ACCESS_LIST {
		if strings.HasPrefix(request, k) {
			if v != nil {
				job_is_defined = true
				job_type = k
				break
			}
		}
	}

	// if job is defined
	if job_is_defined {
		// if user in deny list
		if !ACCESS_LIST[job_type].Policy && ACCESS_LIST[job_type].Users[user] {
			pass = false

		// if user not in allow list
		} else if ACCESS_LIST[job_type].Policy && !ACCESS_LIST[job_type].Users[user] {
			pass = false

		// if user in allow list
		} else if ACCESS_LIST[job_type].Policy && ACCESS_LIST[job_type].Users[user] {
			pass = true

		// if user not in deny list
		} else if !ACCESS_LIST[job_type].Policy && !ACCESS_LIST[job_type].Users[user] {
			pass = true
		}

		// if user is allowed
		if pass {
			// choose response text
			if len(ACCESS_LIST[job_type].Allow_msg) > 0 {
				response_text = ACCESS_LIST[job_type].Allow_msg
			} else {
				response_text = ALLOW_MSG
			}

			// choose class
			if len(ACCESS_LIST[job_type].Class) > 0 {
				class = ACCESS_LIST[job_type].Class
			} else {
				class = CLASS
			}

			// choose queue
			if len(ACCESS_LIST[job_type].Queue) > 0 {
				queue = "resque:queue:" + ACCESS_LIST[job_type].Queue
			} else {
				queue = "resque:queue:" + QUEUE
			}

		// if user is denied
		} else {
			if len(ACCESS_LIST[job_type].Deny_msg) > 0 {
				response_text = ACCESS_LIST[job_type].Deny_msg
			} else {
				response_text = DENY_MSG
			}
		}

	} else {
		// undefined job
		if pass {
			response_text = ALLOW_MSG
			class = CLASS
			queue = "resque:queue:" + QUEUE
		} else {
			response_text = DENY_MSG
		}
	}

	if pass {
		// make connection to redis
		rcon, err := redis.Dial("tcp", REDIS)
		if err != nil {
			fmt.Println("error:", err)
		}

		// create job
		job := Job{
			Class: class,
			Args: []string{
				"request=" + request,
				"user=" + user,
				"channel_name=" + data.Get("channel_name"),
				"timestamp=" + data.Get("timestamp"),
			},
		}
		json_job, err := json.Marshal(job)
		if err != nil {
			fmt.Println("error:", err)
		}
		// push job to resque
		rcon.Cmd("RPUSH", queue, string(json_job))
		rcon.Close()
	}

	// response to slack
	res := Response{
		Text: "@" + user + ": " + response_text,
	}
	json_res, err := json.Marshal(res)
	if err != nil {
		fmt.Println("error:", err)
	}
	w.Header().Set("Content-type", "application/json")
	fmt.Fprintf(w, string(json_res))
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes 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 cmd

import (
	"fmt"
	"strings"
	"time"

	"github.com/golang/glog"
	"github.com/spf13/cobra"

	appsv1 "k8s.io/api/apps/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/uuid"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/dynamic"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	kubectlwait "k8s.io/kubernetes/pkg/kubectl/cmd/wait"
	"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
	"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
	"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
	"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

var (
	delete_long = templates.LongDesc(i18n.T(`
		Delete resources by filenames, stdin, resources and names, or by resources and label selector.

		JSON and YAML formats are accepted. Only one type of the arguments may be specified: filenames,
		resources and names, or resources and label selector.

		Some resources, such as pods, support graceful deletion. These resources define a default period
		before they are forcibly terminated (the grace period) but you may override that value with
		the --grace-period flag, or pass --now to set a grace-period of 1. Because these resources often
		represent entities in the cluster, deletion may not be acknowledged immediately. If the node
		hosting a pod is down or cannot reach the API server, termination may take significantly longer
		than the grace period. To force delete a resource, you must pass a grace period of 0 and specify
		the --force flag.

		IMPORTANT: Force deleting pods does not wait for confirmation that the pod's processes have been
		terminated, which can leave those processes running until the node detects the deletion and
		completes graceful deletion. If your processes use shared storage or talk to a remote API and
		depend on the name of the pod to identify themselves, force deleting those pods may result in
		multiple processes running on different machines using the same identification which may lead
		to data corruption or inconsistency. Only force delete pods when you are sure the pod is
		terminated, or if your application can tolerate multiple copies of the same pod running at once.
		Also, if you force delete pods the scheduler may place new pods on those nodes before the node
		has released those resources and causing those pods to be evicted immediately.

		Note that the delete command does NOT do resource version checks, so if someone submits an
		update to a resource right when you submit a delete, their update will be lost along with the
		rest of the resource.`))

	delete_example = templates.Examples(i18n.T(`
		# Delete a pod using the type and name specified in pod.json.
		kubectl delete -f ./pod.json

		# Delete a pod based on the type and name in the JSON passed into stdin.
		cat pod.json | kubectl delete -f -

		# Delete pods and services with same names "baz" and "foo"
		kubectl delete pod,service baz foo

		# Delete pods and services with label name=myLabel.
		kubectl delete pods,services -l name=myLabel

		# Delete a pod with minimal delay
		kubectl delete pod foo --now

		# Force delete a pod on a dead node
		kubectl delete pod foo --grace-period=0 --force

		# Delete all pods
		kubectl delete pods --all`))
)

type DeleteOptions struct {
	resource.FilenameOptions

	LabelSelector   string
	FieldSelector   string
	DeleteAll       bool
	IgnoreNotFound  bool
	Cascade         bool
	DeleteNow       bool
	ForceDeletion   bool
	WaitForDeletion bool

	GracePeriod int
	Timeout     time.Duration

	Output string

	DynamicClient dynamic.Interface
	Mapper        meta.RESTMapper
	Result        *resource.Result

	genericclioptions.IOStreams
}

func NewCmdDelete(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
	deleteFlags := NewDeleteCommandFlags("containing the resource to delete.")

	cmd := &cobra.Command{
		Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])",
		DisableFlagsInUseLine: true,
		Short:   i18n.T("Delete resources by filenames, stdin, resources and names, or by resources and label selector"),
		Long:    delete_long,
		Example: delete_example,
		Run: func(cmd *cobra.Command, args []string) {
			o := deleteFlags.ToOptions(nil, streams)
			cmdutil.CheckErr(o.Complete(f, args, cmd))
			cmdutil.CheckErr(o.Validate(cmd))
			cmdutil.CheckErr(o.RunDelete())
		},
		SuggestFor: []string{"rm"},
	}

	deleteFlags.AddFlags(cmd)

	cmdutil.AddIncludeUninitializedFlag(cmd)
	return cmd
}

func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Command) error {
	cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
	if err != nil {
		return err
	}

	if o.DeleteAll || len(o.LabelSelector) > 0 || len(o.FieldSelector) > 0 {
		if f := cmd.Flags().Lookup("ignore-not-found"); f != nil && !f.Changed {
			// If the user didn't explicitly set the option, default to ignoring NotFound errors when used with --all, -l, or --field-selector
			o.IgnoreNotFound = true
		}
	}
	if o.DeleteNow {
		if o.GracePeriod != -1 {
			return fmt.Errorf("--now and --grace-period cannot be specified together")
		}
		o.GracePeriod = 1
	}
	if o.GracePeriod == 0 && !o.ForceDeletion {
		// To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0
		// into --grace-period=1. Users may provide --force to bypass this conversion.
		o.GracePeriod = 1
	}

	includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
	r := f.NewBuilder().
		Unstructured().
		ContinueOnError().
		NamespaceParam(cmdNamespace).DefaultNamespace().
		FilenameParam(enforceNamespace, &o.FilenameOptions).
		LabelSelectorParam(o.LabelSelector).
		FieldSelectorParam(o.FieldSelector).
		IncludeUninitialized(includeUninitialized).
		SelectAllParam(o.DeleteAll).
		ResourceTypeOrNameArgs(false, args...).RequireObject(false).
		Flatten().
		Do()
	err = r.Err()
	if err != nil {
		return err
	}
	o.Result = r

	o.Mapper, err = f.ToRESTMapper()
	if err != nil {
		return err
	}

	o.DynamicClient, err = f.DynamicClient()
	if err != nil {
		return err
	}

	return nil
}

func (o *DeleteOptions) Validate(cmd *cobra.Command) error {
	if o.Output != "" && o.Output != "name" {
		return cmdutil.UsageErrorf(cmd, "Unexpected -o output mode: %v. We only support '-o name'.", o.Output)
	}

	if o.DeleteAll && len(o.LabelSelector) > 0 {
		return fmt.Errorf("cannot set --all and --selector at the same time")
	}
	if o.DeleteAll && len(o.FieldSelector) > 0 {
		return fmt.Errorf("cannot set --all and --field-selector at the same time")
	}

	if o.GracePeriod == 0 && !o.ForceDeletion && !o.WaitForDeletion {
		// With the explicit --wait flag we need extra validation for backward compatibility
		return fmt.Errorf("--grace-period=0 must have either --force specified, or --wait to be set to true")
	}

	switch {
	case o.GracePeriod == 0 && o.ForceDeletion:
		fmt.Fprintf(o.ErrOut, "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n")
	case o.ForceDeletion:
		fmt.Fprintf(o.ErrOut, "warning: --force is ignored because --grace-period is not 0.\n")
	}
	return nil
}

func (o *DeleteOptions) RunDelete() error {
	return o.DeleteResult(o.Result)
}

func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
	found := 0
	if o.IgnoreNotFound {
		r = r.IgnoreErrors(errors.IsNotFound)
	}
	err := r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}
		found++

		options := &metav1.DeleteOptions{}
		if o.GracePeriod >= 0 {
			options = metav1.NewDeleteOptions(int64(o.GracePeriod))
		}
		policy := metav1.DeletePropagationForeground
		if !o.Cascade {
			policy = metav1.DeletePropagationOrphan
		}
		options.PropagationPolicy = &policy
		return o.deleteResource(info, options)
	})
	if err != nil {
		return err
	}
	if found == 0 {
		fmt.Fprintf(o.Out, "No resources found\n")
		return nil
	}
	if !o.WaitForDeletion {
		return nil
	}
	// if we don't have a dynamic client, we don't want to wait.  Eventually when delete is cleaned up, this will likely
	// drop out.
	if o.DynamicClient == nil {
		return nil
	}

	effectiveTimeout := o.Timeout
	if effectiveTimeout == 0 {
		// if we requested to wait forever, set it to a week.
		effectiveTimeout = 168 * time.Hour
	}
	waitOptions := kubectlwait.WaitOptions{
		ResourceFinder: genericclioptions.ResourceFinderForResult(r),
		DynamicClient:  o.DynamicClient,
		Timeout:        effectiveTimeout,

		Printer:     printers.NewDiscardingPrinter(),
		ConditionFn: kubectlwait.IsDeleted,
		IOStreams:   o.IOStreams,
	}
	err = waitOptions.RunWait()
	if errors.IsForbidden(err) {
		// if we're forbidden from waiting, we shouldn't fail.
		glog.V(1).Info(err)
		return nil
	}
	return err
}

func (o *DeleteOptions) deleteResource(info *resource.Info, deleteOptions *metav1.DeleteOptions) error {
	// TODO: this should be removed as soon as DaemonSet controller properly handles object deletion
	// see https://github.com/kubernetes/kubernetes/issues/64313 for details
	mapping := info.ResourceMapping()
	if mapping.Resource.GroupResource() == (schema.GroupResource{Group: "extensions", Resource: "daemonsets"}) ||
		mapping.Resource.GroupResource() == (schema.GroupResource{Group: "apps", Resource: "daemonsets"}) {
		if err := updateDaemonSet(info.Namespace, info.Name, o.DynamicClient); err != nil {
			return err
		}
	}

	if err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, deleteOptions); err != nil {
		return cmdutil.AddSourceToErr("deleting", info.Source, err)
	}

	o.PrintObj(info)
	return nil
}

func updateDaemonSet(namespace, name string, dynamicClient dynamic.Interface) error {
	dsClient := dynamicClient.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "daemonsets"}).Namespace(namespace)
	obj, err := dsClient.Get(name, metav1.GetOptions{})
	if err != nil {
		return err
	}
	ds := &appsv1.DaemonSet{}
	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, ds); err != nil {
		return err
	}

	// We set the nodeSelector to a random label. This label is nearly guaranteed
	// to not be set on any node so the DameonSetController will start deleting
	// daemon pods. Once it's done deleting the daemon pods, it's safe to delete
	// the DaemonSet.
	ds.Spec.Template.Spec.NodeSelector = map[string]string{
		string(uuid.NewUUID()): string(uuid.NewUUID()),
	}
	// force update to avoid version conflict
	ds.ResourceVersion = ""

	out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ds)
	if err != nil {
		return err
	}
	if _, err = dsClient.Update(&unstructured.Unstructured{Object: out}); err != nil {
		return err
	}

	// Wait for the daemon set controller to kill all the daemon pods.
	if err := wait.Poll(1*time.Second, 5*time.Minute, func() (bool, error) {
		updatedObj, err := dsClient.Get(name, metav1.GetOptions{})
		if err != nil {
			return false, nil
		}
		updatedDS := &appsv1.DaemonSet{}
		if err := runtime.DefaultUnstructuredConverter.FromUnstructured(updatedObj.Object, ds); err != nil {
			return false, nil
		}

		return updatedDS.Status.CurrentNumberScheduled+updatedDS.Status.NumberMisscheduled == 0, nil
	}); err != nil {
		return err
	}
	return nil
}

// deletion printing is special because we do not have an object to print.
// This mirrors name printer behavior
func (o *DeleteOptions) PrintObj(info *resource.Info) {
	operation := "deleted"
	groupKind := info.Mapping.GroupVersionKind
	kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group)
	if len(groupKind.Group) == 0 {
		kindString = strings.ToLower(groupKind.Kind)
	}

	if o.GracePeriod == 0 {
		operation = "force deleted"
	}

	if o.Output == "name" {
		// -o name: prints resource/name
		fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
		return
	}

	// understandable output by default
	fmt.Fprintf(o.Out, "%s \"%s\" %s\n", kindString, info.Name, operation)
}
<commit_msg>Add TODO for removing kubectl DaemonSet deletion hack<commit_after>/*
Copyright 2014 The Kubernetes 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 cmd

import (
	"fmt"
	"strings"
	"time"

	"github.com/golang/glog"
	"github.com/spf13/cobra"

	appsv1 "k8s.io/api/apps/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/meta"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/uuid"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/dynamic"
	"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	kubectlwait "k8s.io/kubernetes/pkg/kubectl/cmd/wait"
	"k8s.io/kubernetes/pkg/kubectl/genericclioptions"
	"k8s.io/kubernetes/pkg/kubectl/genericclioptions/printers"
	"k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource"
	"k8s.io/kubernetes/pkg/kubectl/util/i18n"
)

var (
	delete_long = templates.LongDesc(i18n.T(`
		Delete resources by filenames, stdin, resources and names, or by resources and label selector.

		JSON and YAML formats are accepted. Only one type of the arguments may be specified: filenames,
		resources and names, or resources and label selector.

		Some resources, such as pods, support graceful deletion. These resources define a default period
		before they are forcibly terminated (the grace period) but you may override that value with
		the --grace-period flag, or pass --now to set a grace-period of 1. Because these resources often
		represent entities in the cluster, deletion may not be acknowledged immediately. If the node
		hosting a pod is down or cannot reach the API server, termination may take significantly longer
		than the grace period. To force delete a resource, you must pass a grace period of 0 and specify
		the --force flag.

		IMPORTANT: Force deleting pods does not wait for confirmation that the pod's processes have been
		terminated, which can leave those processes running until the node detects the deletion and
		completes graceful deletion. If your processes use shared storage or talk to a remote API and
		depend on the name of the pod to identify themselves, force deleting those pods may result in
		multiple processes running on different machines using the same identification which may lead
		to data corruption or inconsistency. Only force delete pods when you are sure the pod is
		terminated, or if your application can tolerate multiple copies of the same pod running at once.
		Also, if you force delete pods the scheduler may place new pods on those nodes before the node
		has released those resources and causing those pods to be evicted immediately.

		Note that the delete command does NOT do resource version checks, so if someone submits an
		update to a resource right when you submit a delete, their update will be lost along with the
		rest of the resource.`))

	delete_example = templates.Examples(i18n.T(`
		# Delete a pod using the type and name specified in pod.json.
		kubectl delete -f ./pod.json

		# Delete a pod based on the type and name in the JSON passed into stdin.
		cat pod.json | kubectl delete -f -

		# Delete pods and services with same names "baz" and "foo"
		kubectl delete pod,service baz foo

		# Delete pods and services with label name=myLabel.
		kubectl delete pods,services -l name=myLabel

		# Delete a pod with minimal delay
		kubectl delete pod foo --now

		# Force delete a pod on a dead node
		kubectl delete pod foo --grace-period=0 --force

		# Delete all pods
		kubectl delete pods --all`))
)

type DeleteOptions struct {
	resource.FilenameOptions

	LabelSelector   string
	FieldSelector   string
	DeleteAll       bool
	IgnoreNotFound  bool
	Cascade         bool
	DeleteNow       bool
	ForceDeletion   bool
	WaitForDeletion bool

	GracePeriod int
	Timeout     time.Duration

	Output string

	DynamicClient dynamic.Interface
	Mapper        meta.RESTMapper
	Result        *resource.Result

	genericclioptions.IOStreams
}

func NewCmdDelete(f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
	deleteFlags := NewDeleteCommandFlags("containing the resource to delete.")

	cmd := &cobra.Command{
		Use: "delete ([-f FILENAME] | TYPE [(NAME | -l label | --all)])",
		DisableFlagsInUseLine: true,
		Short:   i18n.T("Delete resources by filenames, stdin, resources and names, or by resources and label selector"),
		Long:    delete_long,
		Example: delete_example,
		Run: func(cmd *cobra.Command, args []string) {
			o := deleteFlags.ToOptions(nil, streams)
			cmdutil.CheckErr(o.Complete(f, args, cmd))
			cmdutil.CheckErr(o.Validate(cmd))
			cmdutil.CheckErr(o.RunDelete())
		},
		SuggestFor: []string{"rm"},
	}

	deleteFlags.AddFlags(cmd)

	cmdutil.AddIncludeUninitializedFlag(cmd)
	return cmd
}

func (o *DeleteOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Command) error {
	cmdNamespace, enforceNamespace, err := f.ToRawKubeConfigLoader().Namespace()
	if err != nil {
		return err
	}

	if o.DeleteAll || len(o.LabelSelector) > 0 || len(o.FieldSelector) > 0 {
		if f := cmd.Flags().Lookup("ignore-not-found"); f != nil && !f.Changed {
			// If the user didn't explicitly set the option, default to ignoring NotFound errors when used with --all, -l, or --field-selector
			o.IgnoreNotFound = true
		}
	}
	if o.DeleteNow {
		if o.GracePeriod != -1 {
			return fmt.Errorf("--now and --grace-period cannot be specified together")
		}
		o.GracePeriod = 1
	}
	if o.GracePeriod == 0 && !o.ForceDeletion {
		// To preserve backwards compatibility, but prevent accidental data loss, we convert --grace-period=0
		// into --grace-period=1. Users may provide --force to bypass this conversion.
		o.GracePeriod = 1
	}

	includeUninitialized := cmdutil.ShouldIncludeUninitialized(cmd, false)
	r := f.NewBuilder().
		Unstructured().
		ContinueOnError().
		NamespaceParam(cmdNamespace).DefaultNamespace().
		FilenameParam(enforceNamespace, &o.FilenameOptions).
		LabelSelectorParam(o.LabelSelector).
		FieldSelectorParam(o.FieldSelector).
		IncludeUninitialized(includeUninitialized).
		SelectAllParam(o.DeleteAll).
		ResourceTypeOrNameArgs(false, args...).RequireObject(false).
		Flatten().
		Do()
	err = r.Err()
	if err != nil {
		return err
	}
	o.Result = r

	o.Mapper, err = f.ToRESTMapper()
	if err != nil {
		return err
	}

	o.DynamicClient, err = f.DynamicClient()
	if err != nil {
		return err
	}

	return nil
}

func (o *DeleteOptions) Validate(cmd *cobra.Command) error {
	if o.Output != "" && o.Output != "name" {
		return cmdutil.UsageErrorf(cmd, "Unexpected -o output mode: %v. We only support '-o name'.", o.Output)
	}

	if o.DeleteAll && len(o.LabelSelector) > 0 {
		return fmt.Errorf("cannot set --all and --selector at the same time")
	}
	if o.DeleteAll && len(o.FieldSelector) > 0 {
		return fmt.Errorf("cannot set --all and --field-selector at the same time")
	}

	if o.GracePeriod == 0 && !o.ForceDeletion && !o.WaitForDeletion {
		// With the explicit --wait flag we need extra validation for backward compatibility
		return fmt.Errorf("--grace-period=0 must have either --force specified, or --wait to be set to true")
	}

	switch {
	case o.GracePeriod == 0 && o.ForceDeletion:
		fmt.Fprintf(o.ErrOut, "warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.\n")
	case o.ForceDeletion:
		fmt.Fprintf(o.ErrOut, "warning: --force is ignored because --grace-period is not 0.\n")
	}
	return nil
}

func (o *DeleteOptions) RunDelete() error {
	return o.DeleteResult(o.Result)
}

func (o *DeleteOptions) DeleteResult(r *resource.Result) error {
	found := 0
	if o.IgnoreNotFound {
		r = r.IgnoreErrors(errors.IsNotFound)
	}
	err := r.Visit(func(info *resource.Info, err error) error {
		if err != nil {
			return err
		}
		found++

		options := &metav1.DeleteOptions{}
		if o.GracePeriod >= 0 {
			options = metav1.NewDeleteOptions(int64(o.GracePeriod))
		}
		policy := metav1.DeletePropagationForeground
		if !o.Cascade {
			policy = metav1.DeletePropagationOrphan
		}
		options.PropagationPolicy = &policy
		return o.deleteResource(info, options)
	})
	if err != nil {
		return err
	}
	if found == 0 {
		fmt.Fprintf(o.Out, "No resources found\n")
		return nil
	}
	if !o.WaitForDeletion {
		return nil
	}
	// if we don't have a dynamic client, we don't want to wait.  Eventually when delete is cleaned up, this will likely
	// drop out.
	if o.DynamicClient == nil {
		return nil
	}

	effectiveTimeout := o.Timeout
	if effectiveTimeout == 0 {
		// if we requested to wait forever, set it to a week.
		effectiveTimeout = 168 * time.Hour
	}
	waitOptions := kubectlwait.WaitOptions{
		ResourceFinder: genericclioptions.ResourceFinderForResult(r),
		DynamicClient:  o.DynamicClient,
		Timeout:        effectiveTimeout,

		Printer:     printers.NewDiscardingPrinter(),
		ConditionFn: kubectlwait.IsDeleted,
		IOStreams:   o.IOStreams,
	}
	err = waitOptions.RunWait()
	if errors.IsForbidden(err) {
		// if we're forbidden from waiting, we shouldn't fail.
		glog.V(1).Info(err)
		return nil
	}
	return err
}

func (o *DeleteOptions) deleteResource(info *resource.Info, deleteOptions *metav1.DeleteOptions) error {
	// TODO: Remove this in or after 1.12 release.
	//       Server version >= 1.11 no longer needs this hack.
	mapping := info.ResourceMapping()
	if mapping.Resource.GroupResource() == (schema.GroupResource{Group: "extensions", Resource: "daemonsets"}) ||
		mapping.Resource.GroupResource() == (schema.GroupResource{Group: "apps", Resource: "daemonsets"}) {
		if err := updateDaemonSet(info.Namespace, info.Name, o.DynamicClient); err != nil {
			return err
		}
	}

	if err := resource.NewHelper(info.Client, info.Mapping).DeleteWithOptions(info.Namespace, info.Name, deleteOptions); err != nil {
		return cmdutil.AddSourceToErr("deleting", info.Source, err)
	}

	o.PrintObj(info)
	return nil
}

// TODO: Remove this in or after 1.12 release.
//       Server version >= 1.11 no longer needs this hack.
func updateDaemonSet(namespace, name string, dynamicClient dynamic.Interface) error {
	dsClient := dynamicClient.Resource(schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "daemonsets"}).Namespace(namespace)
	obj, err := dsClient.Get(name, metav1.GetOptions{})
	if err != nil {
		return err
	}
	ds := &appsv1.DaemonSet{}
	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, ds); err != nil {
		return err
	}

	// We set the nodeSelector to a random label. This label is nearly guaranteed
	// to not be set on any node so the DameonSetController will start deleting
	// daemon pods. Once it's done deleting the daemon pods, it's safe to delete
	// the DaemonSet.
	ds.Spec.Template.Spec.NodeSelector = map[string]string{
		string(uuid.NewUUID()): string(uuid.NewUUID()),
	}
	// force update to avoid version conflict
	ds.ResourceVersion = ""

	out, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ds)
	if err != nil {
		return err
	}
	if _, err = dsClient.Update(&unstructured.Unstructured{Object: out}); err != nil {
		return err
	}

	// Wait for the daemon set controller to kill all the daemon pods.
	if err := wait.Poll(1*time.Second, 5*time.Minute, func() (bool, error) {
		updatedObj, err := dsClient.Get(name, metav1.GetOptions{})
		if err != nil {
			return false, nil
		}
		updatedDS := &appsv1.DaemonSet{}
		if err := runtime.DefaultUnstructuredConverter.FromUnstructured(updatedObj.Object, ds); err != nil {
			return false, nil
		}

		return updatedDS.Status.CurrentNumberScheduled+updatedDS.Status.NumberMisscheduled == 0, nil
	}); err != nil {
		return err
	}
	return nil
}

// deletion printing is special because we do not have an object to print.
// This mirrors name printer behavior
func (o *DeleteOptions) PrintObj(info *resource.Info) {
	operation := "deleted"
	groupKind := info.Mapping.GroupVersionKind
	kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group)
	if len(groupKind.Group) == 0 {
		kindString = strings.ToLower(groupKind.Kind)
	}

	if o.GracePeriod == 0 {
		operation = "force deleted"
	}

	if o.Output == "name" {
		// -o name: prints resource/name
		fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
		return
	}

	// understandable output by default
	fmt.Fprintf(o.Out, "%s \"%s\" %s\n", kindString, info.Name, operation)
}
<|endoftext|>


<commit_before>package lastkeypair

import (
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
	"github.com/aws/aws-sdk-go/service/kms"
	"encoding/json"
	"time"
	"github.com/pkg/errors"
	"os"
	"strconv"
	"github.com/aws/aws-sdk-go/service/ssm"
	"github.com/aws/aws-sdk-go/aws"
	"encoding/base64"
	"fmt"
	"log"
	"golang.org/x/crypto/ssh"
)

type LambdaConfig struct {
	KeyId string
	KmsTokenIdentity string
	CaKeyBytes []byte
	CaKeyPassphraseBytes []byte
	ValidityDuration int64
	AuthorizationLambda string
}

func getPstoreOrKmsOrRawBytes(name string) ([]byte, error) {
	var bytes []byte

	if pstoreName, found := os.LookupEnv(fmt.Sprintf("PSTORE_%s", name)); found {
		ssmClient := ssm.New(session.New())
		ssmInput := &ssm.GetParametersInput{
			Names: aws.StringSlice([]string{pstoreName}),
			WithDecryption: aws.Bool(true),
		}

		ssmResp, err := ssmClient.GetParameters(ssmInput)
		if err != nil {
			return nil, errors.Wrap(err, "decrypting key bytes from pstore")
		}

		valstr := ssmResp.Parameters[0].Value
		bytes = []byte(*valstr)
	} else if kmsEncrypted, found := os.LookupEnv(fmt.Sprintf("KMS_B64_%s", name)); found {
		kmsClient := kms.New(session.New())

		b64dec, err := base64.StdEncoding.DecodeString(kmsEncrypted)
		if err != nil {
			return nil, errors.Wrap(err, "base64 decoding kms-encrypted ca key bytes")
		}

		kmsInput := &kms.DecryptInput{CiphertextBlob: b64dec}
		kmsResp, err := kmsClient.Decrypt(kmsInput)
		if err != nil {
			return nil, errors.Wrap(err, "decrypting kms-encrypted ca key bytes")
		}

		bytes = kmsResp.Plaintext
	} else if raw, found := os.LookupEnv(name); found {
		bytes = []byte(raw)
	} else {
		return nil, nil
	}

	return bytes, nil
}

func LambdaHandle(evt json.RawMessage) (interface{}, error) {
	caKeyBytes, err := getPstoreOrKmsOrRawBytes("CA_KEY_BYTES")
	if err != nil {
		return nil, err
	} else if caKeyBytes == nil {
		return nil, errors.New("no ca key bytes provided")
	}

	caKeyPassphraseBytes, err := getPstoreOrKmsOrRawBytes("CA_KEY_PASSPHRASE_BYTES")
	if err != nil {
		return nil, err
	}

	validity, err := strconv.ParseInt(os.Getenv("VALIDITY_DURATION"), 10, 64)

	kmsTokenIdentity := os.Getenv("KMS_TOKEN_IDENTITY")
	if len(kmsTokenIdentity) == 0 {
		kmsTokenIdentity = "LastKeypair"
	}

	config := LambdaConfig{
		KeyId: os.Getenv("KMS_KEY_ID"),
		KmsTokenIdentity: kmsTokenIdentity,
		CaKeyBytes: caKeyBytes,
		CaKeyPassphraseBytes: caKeyPassphraseBytes,
		ValidityDuration: validity,
		AuthorizationLambda: os.Getenv("AUTHORIZATION_LAMBDA"),
	}

	raw := make(map[string]string)
	json.Unmarshal(evt, &raw)

	switch raw["EventType"] {
	case "UserCertReq":
		req := UserCertReqJson{}
		err := json.Unmarshal(evt, &req)
		if err != nil {
			return nil, errors.Wrap(err, "unmarshalling input")
		}
		return DoUserCertReq(req, config)
	case "HostCertReq":
		req := HostCertReqJson{}
		err := json.Unmarshal(evt, &req)
		if err != nil {
			return nil, errors.Wrap(err, "unmarshalling input")
		}
		return DoHostCertReq(req, config)
	default:
		return nil, errors.New("unexpected event type")
	}
}

func LambdaAwsSession() *session.Session {
	sessOpts := session.Options{
		SharedConfigState: session.SharedConfigEnable,
		AssumeRoleTokenProvider: stscreds.StdinTokenProvider,
	}

	sess, err := session.NewSessionWithOptions(sessOpts)
	if err != nil {
		log.Panicf("couldn't create aws session")
	}

	return sess
}

func DoHostCertReq(req HostCertReqJson, config LambdaConfig) (*HostCertRespJson, error) {
	sess := LambdaAwsSession()

	if !ValidateToken(sess, req.Token, config.KeyId) {
		return nil, errors.New("invalid token")
	}

	permissions := ssh.Permissions{
		CriticalOptions: map[string]string{},
		Extensions: map[string]string{},
	}

	authLambda := NewAuthorizationLambda(config)
	auth, err := authLambda.DoHostReq(req)

	signed, err := SignSsh(
		config.CaKeyBytes,
		config.CaKeyPassphraseBytes,
		[]byte(req.PublicKey),
		ssh.HostCert,
		ssh.CertTimeInfinity,
		permissions,
		auth.KeyId,
		auth.Principals,
	)

	if err != nil {
		return nil, errors.Wrap(err, "signing ssh key")
	}

	resp := HostCertRespJson{
		SignedHostPublicKey: *signed,
	}

	return &resp, nil
}

func DoUserCertReq(req UserCertReqJson, config LambdaConfig) (*UserCertRespJson, error) {
	sess := LambdaAwsSession()

	if !ValidateToken(sess, req.Token, config.KeyId) {
		return nil, errors.New("invalid token")
	}

	identity := req.Token.Params.FromId
	if name := req.Token.Params.FromName; len(name) > 0 {
		identity = fmt.Sprintf("%s-%s", name, identity)
	}

	instanceArn := req.Token.Params.RemoteInstanceArn
	if len(instanceArn) == 0 {
		return nil, errors.New("target instance arn must be specified")
	}

	authLambda := NewAuthorizationLambda(config)
	auth, err := authLambda.DoUserReq(req)
	if err != nil {
		return nil, errors.Wrap(err, "authorising user cert")
	}

	if !auth.Authorized {
		errorMessage := "authorisation denied by auth lambda"
		if auth.Message {
			errorMessage := auth.Message
		}
		return nil, errors.New("authorisation denied by auth lambda")
	}

	permissions := DefaultSshPermissions
	if auth.CertificateOptions.ForceCommand != nil {
		permissions.Extensions["force-command"] = *auth.CertificateOptions.ForceCommand
	}
	if auth.CertificateOptions.SourceAddress != nil {
		permissions.Extensions["source-address"] = *auth.CertificateOptions.SourceAddress
	}
	if auth.CertificateOptions.PermitPortForwarding == false {
		delete(permissions.Extensions, "permit-port-forwarding")
	}
	if auth.CertificateOptions.PermitX11Forwarding == false {
		delete(permissions.Extensions, "permit-X11-forwarding")
	}

	signed, err := SignSsh(
		config.CaKeyBytes,
		config.CaKeyPassphraseBytes,
		[]byte(req.PublicKey),
		ssh.UserCert,
		uint64(time.Now().Unix() + config.ValidityDuration),
		DefaultSshPermissions,
		identity,
		auth.Principals,
	)

	if err != nil {
		return nil, errors.Wrap(err, "signing ssh key")
	}

	expiry := time.Now().Add(time.Duration(config.ValidityDuration) * time.Second)

	resp := UserCertRespJson{
		SignedPublicKey: *signed,
		Jumpboxes: auth.Jumpboxes,
		TargetAddress: auth.TargetAddress,
		Expiry: expiry.Unix(),
	}

	return &resp, nil
}
<commit_msg>Revert "Add custom custom authoriser error messages"<commit_after>package lastkeypair

import (
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
	"github.com/aws/aws-sdk-go/service/kms"
	"encoding/json"
	"time"
	"github.com/pkg/errors"
	"os"
	"strconv"
	"github.com/aws/aws-sdk-go/service/ssm"
	"github.com/aws/aws-sdk-go/aws"
	"encoding/base64"
	"fmt"
	"log"
	"golang.org/x/crypto/ssh"
)

type LambdaConfig struct {
	KeyId string
	KmsTokenIdentity string
	CaKeyBytes []byte
	CaKeyPassphraseBytes []byte
	ValidityDuration int64
	AuthorizationLambda string
}

func getPstoreOrKmsOrRawBytes(name string) ([]byte, error) {
	var bytes []byte

	if pstoreName, found := os.LookupEnv(fmt.Sprintf("PSTORE_%s", name)); found {
		ssmClient := ssm.New(session.New())
		ssmInput := &ssm.GetParametersInput{
			Names: aws.StringSlice([]string{pstoreName}),
			WithDecryption: aws.Bool(true),
		}

		ssmResp, err := ssmClient.GetParameters(ssmInput)
		if err != nil {
			return nil, errors.Wrap(err, "decrypting key bytes from pstore")
		}

		valstr := ssmResp.Parameters[0].Value
		bytes = []byte(*valstr)
	} else if kmsEncrypted, found := os.LookupEnv(fmt.Sprintf("KMS_B64_%s", name)); found {
		kmsClient := kms.New(session.New())

		b64dec, err := base64.StdEncoding.DecodeString(kmsEncrypted)
		if err != nil {
			return nil, errors.Wrap(err, "base64 decoding kms-encrypted ca key bytes")
		}

		kmsInput := &kms.DecryptInput{CiphertextBlob: b64dec}
		kmsResp, err := kmsClient.Decrypt(kmsInput)
		if err != nil {
			return nil, errors.Wrap(err, "decrypting kms-encrypted ca key bytes")
		}

		bytes = kmsResp.Plaintext
	} else if raw, found := os.LookupEnv(name); found {
		bytes = []byte(raw)
	} else {
		return nil, nil
	}

	return bytes, nil
}

func LambdaHandle(evt json.RawMessage) (interface{}, error) {
	caKeyBytes, err := getPstoreOrKmsOrRawBytes("CA_KEY_BYTES")
	if err != nil {
		return nil, err
	} else if caKeyBytes == nil {
		return nil, errors.New("no ca key bytes provided")
	}

	caKeyPassphraseBytes, err := getPstoreOrKmsOrRawBytes("CA_KEY_PASSPHRASE_BYTES")
	if err != nil {
		return nil, err
	}

	validity, err := strconv.ParseInt(os.Getenv("VALIDITY_DURATION"), 10, 64)

	kmsTokenIdentity := os.Getenv("KMS_TOKEN_IDENTITY")
	if len(kmsTokenIdentity) == 0 {
		kmsTokenIdentity = "LastKeypair"
	}

	config := LambdaConfig{
		KeyId: os.Getenv("KMS_KEY_ID"),
		KmsTokenIdentity: kmsTokenIdentity,
		CaKeyBytes: caKeyBytes,
		CaKeyPassphraseBytes: caKeyPassphraseBytes,
		ValidityDuration: validity,
		AuthorizationLambda: os.Getenv("AUTHORIZATION_LAMBDA"),
	}

	raw := make(map[string]string)
	json.Unmarshal(evt, &raw)

	switch raw["EventType"] {
	case "UserCertReq":
		req := UserCertReqJson{}
		err := json.Unmarshal(evt, &req)
		if err != nil {
			return nil, errors.Wrap(err, "unmarshalling input")
		}
		return DoUserCertReq(req, config)
	case "HostCertReq":
		req := HostCertReqJson{}
		err := json.Unmarshal(evt, &req)
		if err != nil {
			return nil, errors.Wrap(err, "unmarshalling input")
		}
		return DoHostCertReq(req, config)
	default:
		return nil, errors.New("unexpected event type")
	}
}

func LambdaAwsSession() *session.Session {
	sessOpts := session.Options{
		SharedConfigState: session.SharedConfigEnable,
		AssumeRoleTokenProvider: stscreds.StdinTokenProvider,
	}

	sess, err := session.NewSessionWithOptions(sessOpts)
	if err != nil {
		log.Panicf("couldn't create aws session")
	}

	return sess
}

func DoHostCertReq(req HostCertReqJson, config LambdaConfig) (*HostCertRespJson, error) {
	sess := LambdaAwsSession()

	if !ValidateToken(sess, req.Token, config.KeyId) {
		return nil, errors.New("invalid token")
	}

	permissions := ssh.Permissions{
		CriticalOptions: map[string]string{},
		Extensions: map[string]string{},
	}

	authLambda := NewAuthorizationLambda(config)
	auth, err := authLambda.DoHostReq(req)

	signed, err := SignSsh(
		config.CaKeyBytes,
		config.CaKeyPassphraseBytes,
		[]byte(req.PublicKey),
		ssh.HostCert,
		ssh.CertTimeInfinity,
		permissions,
		auth.KeyId,
		auth.Principals,
	)

	if err != nil {
		return nil, errors.Wrap(err, "signing ssh key")
	}

	resp := HostCertRespJson{
		SignedHostPublicKey: *signed,
	}

	return &resp, nil
}

func DoUserCertReq(req UserCertReqJson, config LambdaConfig) (*UserCertRespJson, error) {
	sess := LambdaAwsSession()

	if !ValidateToken(sess, req.Token, config.KeyId) {
		return nil, errors.New("invalid token")
	}

	identity := req.Token.Params.FromId
	if name := req.Token.Params.FromName; len(name) > 0 {
		identity = fmt.Sprintf("%s-%s", name, identity)
	}

	instanceArn := req.Token.Params.RemoteInstanceArn
	if len(instanceArn) == 0 {
		return nil, errors.New("target instance arn must be specified")
	}

	authLambda := NewAuthorizationLambda(config)
	auth, err := authLambda.DoUserReq(req)
	if err != nil {
		return nil, errors.Wrap(err, "authorising user cert")
	}

	if !auth.Authorized {
		return nil, errors.New("authorisation denied by auth lambda")
	}

	permissions := DefaultSshPermissions
	if auth.CertificateOptions.ForceCommand != nil {
		permissions.Extensions["force-command"] = *auth.CertificateOptions.ForceCommand
	}
	if auth.CertificateOptions.SourceAddress != nil {
		permissions.Extensions["source-address"] = *auth.CertificateOptions.SourceAddress
	}
	if auth.CertificateOptions.PermitPortForwarding == false {
		delete(permissions.Extensions, "permit-port-forwarding")
	}
	if auth.CertificateOptions.PermitX11Forwarding == false {
		delete(permissions.Extensions, "permit-X11-forwarding")
	}

	signed, err := SignSsh(
		config.CaKeyBytes,
		config.CaKeyPassphraseBytes,
		[]byte(req.PublicKey),
		ssh.UserCert,
		uint64(time.Now().Unix() + config.ValidityDuration),
		DefaultSshPermissions,
		identity,
		auth.Principals,
	)

	if err != nil {
		return nil, errors.Wrap(err, "signing ssh key")
	}

	expiry := time.Now().Add(time.Duration(config.ValidityDuration) * time.Second)

	resp := UserCertRespJson{
		SignedPublicKey: *signed,
		Jumpboxes: auth.Jumpboxes,
		TargetAddress: auth.TargetAddress,
		Expiry: expiry.Unix(),
	}

	return &resp, nil
}
<|endoftext|>


<commit_before>package net

import (
	"context"
	"crypto/tls"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"strings"
	"time"

	igd "github.com/emersion/go-upnp-igd"
	"golang.org/x/net/http2"

	"nimona.io/internal/log"
	"nimona.io/pkg/crypto"
)

type httpTransport struct {
	local   *LocalInfo
	address string
}

func NewHTTPTransport(
	local *LocalInfo,
	address string,
) Transport {
	return &httpTransport{
		local:   local,
		address: address,
	}
}

func (tt *httpTransport) Dial(
	ctx context.Context,
	address string,
) (
	*Connection,
	error,
) {
	address = strings.Replace(address, "https:", "https://", 1)
	address = strings.Replace(address, ":443", "", 1)

	tr := &http2.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true, // nolint: gosec
		},
		AllowHTTP: true,
	}
	client := &http.Client{
		Transport: tr,
	}
	pr, pw := io.Pipe()
	req, _ := http.NewRequest("GET", address, ioutil.NopCloser(pr))
	req.ContentLength = -1
	res, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	rw := &connWrapper{
		w: flushWriter{pw},
		r: res.Body,
		c: res.Body,
	}

	conn := &Connection{
		Conn:          rw,
		RemotePeerKey: nil, // we don't really know who the other side is
	}

	return conn, nil
}

func (tt *httpTransport) Listen(
	ctx context.Context,
) (
	chan *Connection,
	error,
) {

	logger := log.FromContext(ctx).Named("transport/https")

	cert, err := crypto.GenerateCertificate(tt.local.GetPeerKey())
	if err != nil {
		return nil, err
	}

	config := &tls.Config{
		NextProtos:   []string{"h2"},
		Certificates: []tls.Certificate{*cert},
	}

	cconn := make(chan *Connection, 10)

	handler := func(w http.ResponseWriter, r *http.Request) {
		wf, ok := w.(http.Flusher)
		if !ok {
			http.Error(w, "only h2 is supported", 400)
			return
		}

		wf.Flush()

		rw := &connWrapper{
			w: flushWriter{w},
			r: r.Body,
			c: r.Body,
		}

		conn := &Connection{
			Conn:          rw,
			RemotePeerKey: nil,
			IsIncoming:    true,
		}

		cconn <- conn

		<-r.Cancel // TODO is this the right way to wait here?
	}

	srv := &http.Server{
		Addr:    tt.address,
		Handler: http.HandlerFunc(handler),
	}

	netListener, err := net.Listen("tcp", tt.address)
	if err != nil {
		return nil, err
	}

	tlsListener := tls.NewListener(
		tcpKeepAliveListener{
			netListener.(*net.TCPListener),
		},
		config,
	)

	go func() {
		err := srv.Serve(tlsListener)
		if err != nil {
			logger.Error("http transport stopped", log.Error(err))
		}
	}()

	port := netListener.Addr().(*net.TCPAddr).Port
	logger.Info("HTTP tranport listening", log.Int("port", port))
	addresses := GetAddresses("https", netListener)
	devices := make(chan igd.Device, 10)

	if tt.local.GetHostname() != "" {
		addresses = append(addresses, fmtAddress("https", tt.local.GetHostname(), port))
	}

	if UseUPNP {
		logger.Info("Trying to find external IP and open port")
		go func() {
			if err := igd.Discover(devices, 2*time.Second); err != nil {
				logger.Error("could not discover devices", log.Error(err))
			}
		}()
		for device := range devices {
			externalAddress, err := device.GetExternalIPAddress()
			if err != nil {
				logger.Error("could not get external ip", log.Error(err))
				continue
			}
			desc := "nimona-http"
			ttl := time.Hour * 24 * 365
			if _, err := device.AddPortMapping(igd.TCP, port, port, desc, ttl); err != nil {
				logger.Error("could not add port mapping", log.Error(err))
			} else {
				addresses = append(addresses, fmtAddress("https", externalAddress.String(), port))
			}
		}
	}

	tt.local.AddAddress(addresses...)

	logger.Info(
		"Started listening",
		log.Strings("addresses", addresses),
		log.Int("port", port),
	)

	return cconn, nil
}

type flushWriter struct {
	w io.Writer
}

func (fw flushWriter) Write(p []byte) (n int, err error) {
	n, err = fw.w.Write(p)
	if f, ok := fw.w.(http.Flusher); ok {
		f.Flush()
	}
	return
}

func (fw flushWriter) Flush() {
	if f, ok := fw.w.(http.Flusher); ok {
		f.Flush()
	}
}

type connWrapper struct {
	r io.Reader
	w io.Writer
	c io.Closer
}

func (fw connWrapper) Write(p []byte) (int, error) {
	n, err := fw.w.Write(p)
	if f, ok := fw.w.(http.Flusher); ok {
		f.Flush()
	}
	return n, err
}

func (fw connWrapper) Read(b []byte) (int, error) {
	return fw.r.Read(b)
}

func (fw connWrapper) Close() error {
	return fw.c.Close()
}

// From https://golang.org/src/net/http/server.go
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
type tcpKeepAliveListener struct {
	*net.TCPListener
}

func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
	tc, err := ln.AcceptTCP()
	if err != nil {
		return
	}
	if err := tc.SetKeepAlive(true); err != nil {
		return nil, err
	}
	if err := tc.SetKeepAlivePeriod(time.Minute); err != nil {
		return nil, err
	}
	return tc, nil
}
<commit_msg>fix(net): configure http transport<commit_after>package net

import (
	"context"
	"crypto/tls"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"strings"
	"time"

	igd "github.com/emersion/go-upnp-igd"
	"golang.org/x/net/http2"

	"nimona.io/internal/log"
	"nimona.io/pkg/crypto"
)

type httpTransport struct {
	local   *LocalInfo
	address string
}

func NewHTTPTransport(
	local *LocalInfo,
	address string,
) Transport {
	return &httpTransport{
		local:   local,
		address: address,
	}
}

func (tt *httpTransport) Dial(
	ctx context.Context,
	address string,
) (
	*Connection,
	error,
) {
	address = strings.Replace(address, "https:", "https://", 1)
	address = strings.Replace(address, ":443", "", 1)

	tr := &http2.Transport{
		TLSClientConfig: &tls.Config{
			InsecureSkipVerify: true, // nolint: gosec
		},
		AllowHTTP: true,
	}
	http2.ConfigureTransport(tr)
	client := &http.Client{
		Transport: tr,
	}
	pr, pw := io.Pipe()
	req, _ := http.NewRequest("GET", address, ioutil.NopCloser(pr))
	req.ContentLength = -1
	res, err := client.Do(req)
	if err != nil {
		return nil, err
	}

	rw := &connWrapper{
		w: flushWriter{pw},
		r: res.Body,
		c: res.Body,
	}

	conn := &Connection{
		Conn:          rw,
		RemotePeerKey: nil, // we don't really know who the other side is
	}

	return conn, nil
}

func (tt *httpTransport) Listen(
	ctx context.Context,
) (
	chan *Connection,
	error,
) {

	logger := log.FromContext(ctx).Named("transport/https")

	cert, err := crypto.GenerateCertificate(tt.local.GetPeerKey())
	if err != nil {
		return nil, err
	}

	config := &tls.Config{
		NextProtos:   []string{"h2"},
		Certificates: []tls.Certificate{*cert},
	}

	cconn := make(chan *Connection, 10)

	handler := func(w http.ResponseWriter, r *http.Request) {
		wf, ok := w.(http.Flusher)
		if !ok {
			http.Error(w, "only h2 is supported", 400)
			return
		}

		wf.Flush()

		rw := &connWrapper{
			w: flushWriter{w},
			r: r.Body,
			c: r.Body,
		}

		conn := &Connection{
			Conn:          rw,
			RemotePeerKey: nil,
			IsIncoming:    true,
		}

		cconn <- conn

		<-r.Cancel // TODO is this the right way to wait here?
	}

	srv := &http.Server{
		Addr:    tt.address,
		Handler: http.HandlerFunc(handler),
	}

	netListener, err := net.Listen("tcp", tt.address)
	if err != nil {
		return nil, err
	}

	tlsListener := tls.NewListener(
		tcpKeepAliveListener{
			netListener.(*net.TCPListener),
		},
		config,
	)

	go func() {
		err := srv.Serve(tlsListener)
		if err != nil {
			logger.Error("http transport stopped", log.Error(err))
		}
	}()

	port := netListener.Addr().(*net.TCPAddr).Port
	logger.Info("HTTP tranport listening", log.Int("port", port))
	addresses := GetAddresses("https", netListener)
	devices := make(chan igd.Device, 10)

	if tt.local.GetHostname() != "" {
		addresses = append(addresses, fmtAddress("https", tt.local.GetHostname(), port))
	}

	if UseUPNP {
		logger.Info("Trying to find external IP and open port")
		go func() {
			if err := igd.Discover(devices, 2*time.Second); err != nil {
				logger.Error("could not discover devices", log.Error(err))
			}
		}()
		for device := range devices {
			externalAddress, err := device.GetExternalIPAddress()
			if err != nil {
				logger.Error("could not get external ip", log.Error(err))
				continue
			}
			desc := "nimona-http"
			ttl := time.Hour * 24 * 365
			if _, err := device.AddPortMapping(igd.TCP, port, port, desc, ttl); err != nil {
				logger.Error("could not add port mapping", log.Error(err))
			} else {
				addresses = append(addresses, fmtAddress("https", externalAddress.String(), port))
			}
		}
	}

	tt.local.AddAddress(addresses...)

	logger.Info(
		"Started listening",
		log.Strings("addresses", addresses),
		log.Int("port", port),
	)

	return cconn, nil
}

type flushWriter struct {
	w io.Writer
}

func (fw flushWriter) Write(p []byte) (n int, err error) {
	n, err = fw.w.Write(p)
	if f, ok := fw.w.(http.Flusher); ok {
		f.Flush()
	}
	return
}

func (fw flushWriter) Flush() {
	if f, ok := fw.w.(http.Flusher); ok {
		f.Flush()
	}
}

type connWrapper struct {
	r io.Reader
	w io.Writer
	c io.Closer
}

func (fw connWrapper) Write(p []byte) (int, error) {
	n, err := fw.w.Write(p)
	if f, ok := fw.w.(http.Flusher); ok {
		f.Flush()
	}
	return n, err
}

func (fw connWrapper) Read(b []byte) (int, error) {
	return fw.r.Read(b)
}

func (fw connWrapper) Close() error {
	return fw.c.Close()
}

// From https://golang.org/src/net/http/server.go
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
type tcpKeepAliveListener struct {
	*net.TCPListener
}

func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {
	tc, err := ln.AcceptTCP()
	if err != nil {
		return
	}
	if err := tc.SetKeepAlive(true); err != nil {
		return nil, err
	}
	if err := tc.SetKeepAlivePeriod(time.Minute); err != nil {
		return nil, err
	}
	return tc, nil
}
<|endoftext|>


<commit_before>/*
Package role manages roles in Kvdb and provides validation
Copyright 2018 Portworx

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 role

import (
	"context"
	"fmt"
	"reflect"
	"strings"

	"github.com/portworx/kvdb"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/libopenstorage/openstorage/api"
)

const (
	rolePrefix   = "cluster/roles"
	invalidChars = "/ "
)

var (
	// Default roles. Should be prefixed by `system.` to avoid collisions
	defaultRoles = map[string][]*api.SdkRule{
		// system:admin role can run any command
		"system.admin": {
			&api.SdkRule{
				Services: []string{"*"},
				Apis:     []string{"*"},
			},
		},

		// system:view role can only run read-only commands
		"system.view": {
			&api.SdkRule{
				Services: []string{"*"},
				Apis: []string{
					"*enumerate*",
					"inspect*",
					"stats",
					"status",
					"validate",
					"capacityusage",
				},
			},
			&api.SdkRule{
				Services: []string{"identity"},
				Apis:     []string{"*"},
			},
		},

		// system:user role can only access volume lifecycle commands
		"system.user": {
			&api.SdkRule{
				Services: []string{
					"volume",
					"cloudbackup",
					"credentials",
					"objectstore",
					"schedulepolicy",
					"mountattach",
					"migrate",
				},
				Apis: []string{"*"},
			},
			&api.SdkRule{
				Services: []string{"identity"},
				Apis:     []string{"*"},
			},
			&api.SdkRule{
				Services: []string{"policy"},
				Apis: []string{
					"*enumerate*",
					// This will allow system.user to view default policy also
					"*inspect*",
				},
			},
		},
	}
)

// SdkRoleManager is an implementation of the RoleManager for the SDK
type SdkRoleManager struct {
	kv kvdb.Kvdb
}

// Check interface
var _ RoleManager = &SdkRoleManager{}

// Simple function which creates key for Kvdb
func prefixWithName(name string) string {
	return rolePrefix + "/" + name
}

// Determines if the rules apply to string s
// rule can be:
// '*' - match all
// '*xxx' - ends with xxx
// 'xxx*' - starts with xxx
// '*xxx*' - contains xxx
func matchRule(rule, s string) bool {
	// no rule
	rl := len(rule)
	if rl == 0 {
		return false
	}

	// '*xxx' || 'xxx*'
	if rule[0:1] == "*" || rule[rl-1:rl] == "*" {
		// get the matching string from the rule
		match := strings.TrimSpace(strings.Join(strings.Split(rule, "*"), ""))

		// '*' or '*******'
		if len(match) == 0 {
			return true
		}

		// '*xxx*'
		if rule[0:1] == "*" && rule[rl-1:rl] == "*" {
			return strings.Contains(s, match)
		}

		// '*xxx'
		if rule[0:1] == "*" {
			return strings.HasSuffix(s, match)
		}

		// 'xxx*'
		return strings.HasPrefix(s, match)
	}

	// no wildcard stars given in rule
	return rule == s
}

// NewSdkRoleManager returns a new SDK role manager
func NewSdkRoleManager(kv kvdb.Kvdb) (*SdkRoleManager, error) {
	s := &SdkRoleManager{
		kv: kv,
	}

	// Load all default roles
	for k, v := range defaultRoles {
		role := &api.SdkRole{
			Name:  k,
			Rules: v,
		}
		if _, err := kv.Put(prefixWithName(k), role, 0); err != nil {
			return nil, err
		}
	}

	return s, nil
}

// Create saves a role in Kvdb
func (r *SdkRoleManager) Create(
	ctx context.Context,
	req *api.SdkRoleCreateRequest,
) (*api.SdkRoleCreateResponse, error) {
	if req.GetRole() == nil {
		return nil, status.Error(codes.InvalidArgument, "Must supply a role")
	} else if len(req.GetRole().GetName()) == 0 {
		return nil, status.Error(codes.InvalidArgument, "Must supply a name for the role")
	} else if err := r.validateRole(req.GetRole()); err != nil {
		return nil, err
	}

	// Determine if there is collision with default roles
	if _, ok := defaultRoles[req.GetRole().GetName()]; ok {
		return nil, status.Errorf(
			codes.InvalidArgument,
			"Name %s already used by system role", req.GetRole().GetName())
	}

	// Save value in kvdb
	_, err := r.kv.Create(prefixWithName(req.GetRole().GetName()), req.GetRole(), 0)
	if err == kvdb.ErrExist {
		// Idempotency check.
		// Check that the new rules are the same.
		oldrole, err := r.Inspect(ctx, &api.SdkRoleInspectRequest{
			Name: req.GetRole().GetName(),
		})
		if err != nil {
			return nil, err
		}
		if !reflect.DeepEqual(oldrole.GetRole(), req.GetRole()) {
			return nil, status.Error(
				codes.AlreadyExists,
				"Existing role differs from requested role")
		}
	} else if err != nil {
		return nil, status.Errorf(codes.Internal, "Failed to save role: %v", err)
	}

	return &api.SdkRoleCreateResponse{
		Role: req.GetRole(),
	}, nil
}

// Enumerate returns a list of role names
func (r *SdkRoleManager) Enumerate(
	ctx context.Context,
	req *api.SdkRoleEnumerateRequest,
) (*api.SdkRoleEnumerateResponse, error) {
	keys, err := r.kv.Keys(rolePrefix, "/")
	if err != nil {
		return nil, status.Errorf(codes.Internal, "Failed to access roles from database: %v", err)
	}

	names := make([]string, len(keys))
	for i, key := range keys {
		names[i] = strings.TrimPrefix(key, rolePrefix+"/")
	}

	return &api.SdkRoleEnumerateResponse{
		Names: names,
	}, nil
}

// Inspect returns a role object
func (r *SdkRoleManager) Inspect(
	ctx context.Context,
	req *api.SdkRoleInspectRequest,
) (*api.SdkRoleInspectResponse, error) {
	if len(req.GetName()) == 0 {
		return nil, status.Error(codes.InvalidArgument, "Must supply a name for role")
	}

	elem := &api.SdkRole{}
	_, err := r.kv.GetVal(prefixWithName(req.GetName()), elem)
	if err == kvdb.ErrNotFound {
		return nil, status.Errorf(codes.NotFound, "Role %s not found", req.GetName())
	} else if err != nil {
		return nil, status.Errorf(codes.Internal, "Failed to get role %s information: %v", req.GetName(), err)
	}

	return &api.SdkRoleInspectResponse{
		Role: elem,
	}, nil
}

// Delete removes a role from Kvdb
func (r *SdkRoleManager) Delete(
	ctx context.Context,
	req *api.SdkRoleDeleteRequest,
) (*api.SdkRoleDeleteResponse, error) {
	if len(req.GetName()) == 0 {
		return nil, status.Error(codes.InvalidArgument, "Must supply a name for role")
	}

	// Determine if there is collision with default roles
	if _, ok := defaultRoles[req.GetName()]; ok {
		return nil, status.Errorf(
			codes.InvalidArgument,
			"Cannot delete system role %s", req.GetName())
	}

	_, err := r.kv.Delete(prefixWithName(req.GetName()))
	if err != kvdb.ErrNotFound && err != nil {
		return nil, status.Errorf(codes.Internal, "Failed to delete role %s: %v", req.GetName(), err)
	}

	return &api.SdkRoleDeleteResponse{}, nil
}

// Update replaces an existing role.
func (r *SdkRoleManager) Update(
	ctx context.Context,
	req *api.SdkRoleUpdateRequest,
) (*api.SdkRoleUpdateResponse, error) {
	if req.GetRole() == nil {
		return nil, status.Error(codes.InvalidArgument, "Must supply a role")
	}
	if err := r.validateRole(req.GetRole()); err != nil {
		return nil, err
	}

	// Determine if there is collision with default roles
	if _, ok := defaultRoles[req.GetRole().GetName()]; ok {
		return nil, status.Errorf(
			codes.InvalidArgument,
			"System role %s cannot be updated", req.GetRole().GetName())
	}

	_, err := r.kv.Update(prefixWithName(req.GetRole().GetName()), req.GetRole(), 0)
	if err == kvdb.ErrNotFound {
		return nil, status.Errorf(codes.NotFound, "Role %s not found", req.GetRole())
	} else if err != nil {
		return nil, status.Errorf(codes.Internal, "Failed to get role %s information: %v", req.GetRole().GetName(), err)
	}

	return &api.SdkRoleUpdateResponse{
		Role: req.GetRole(),
	}, nil
}

// Verify determines if the role has access to `fullmethod`
func (r *SdkRoleManager) Verify(ctx context.Context, roles []string, fullmethod string) error {

	// Check all roles
	for _, role := range roles {
		// Get the role rules
		resp, err := r.Inspect(ctx, &api.SdkRoleInspectRequest{
			Name: role,
		})
		if err != nil || resp == nil || resp.GetRole() == nil {
			continue
		}

		if err := r.verifyRules(resp.GetRole().GetRules(), fullmethod); err == nil {
			return nil
		}
	}

	return status.Errorf(codes.PermissionDenied, "Access denied to roles: %+s", roles)
}

// verifyRules checks if the rules authorize use of the API called `fullmethod`
func (r *SdkRoleManager) verifyRules(rules []*api.SdkRule, fullmethod string) error {
	var reqService, reqApi string

	// String: "/openstorage.api.OpenStorage<service>/<method>"
	parts := strings.Split(fullmethod, "/")

	if len(parts) > 1 {
		reqService = strings.TrimPrefix(strings.ToLower(parts[1]), "openstorage.api.openstorage")
	}

	if len(parts) > 2 {
		reqApi = strings.ToLower(parts[2])
	}

	// Go through each rule until a match is found
	for _, rule := range rules {
		for _, service := range rule.Services {
			if matchRule(service, reqService) {
				for _, api := range rule.Apis {
					if matchRule(api, reqApi) {
						return nil
					}
				}
			}
		}
	}

	return fmt.Errorf("no accessable rule to authorize access found")
}

func (r *SdkRoleManager) validateRole(role *api.SdkRole) error {
	if len(role.GetName()) == 0 {
		return status.Error(codes.InvalidArgument, "Must supply a name for role")
	} else if len(role.GetRules()) == 0 {
		return status.Error(codes.InvalidArgument, "Must supply rules for role")
	} else if strings.ContainsAny(role.GetName(), invalidChars) {
		return status.Errorf(codes.InvalidArgument, "Name cannot contain any of the following invalid characters: %s", invalidChars)
	}

	// Check if the rules have services and apis
	for _, rule := range role.GetRules() {
		if len(rule.GetServices()) == 0 {
			return status.Error(codes.InvalidArgument, "Must supply a service in rules for the role")
		} else if len(rule.GetApis()) == 0 {
			return status.Error(codes.InvalidArgument, "Must supply apis in rules for the role")
		}
	}

	return nil
}
<commit_msg>Fix role enumerate to return superstrings<commit_after>/*
Package role manages roles in Kvdb and provides validation
Copyright 2018 Portworx

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 role

import (
	"context"
	"fmt"
	"reflect"
	"strings"

	"github.com/portworx/kvdb"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"

	"github.com/libopenstorage/openstorage/api"
)

const (
	rolePrefix   = "cluster/roles"
	invalidChars = "/ "
)

var (
	// Default roles. Should be prefixed by `system.` to avoid collisions
	defaultRoles = map[string][]*api.SdkRule{
		// system:admin role can run any command
		"system.admin": {
			&api.SdkRule{
				Services: []string{"*"},
				Apis:     []string{"*"},
			},
		},

		// system:view role can only run read-only commands
		"system.view": {
			&api.SdkRule{
				Services: []string{"*"},
				Apis: []string{
					"*enumerate*",
					"inspect*",
					"stats",
					"status",
					"validate",
					"capacityusage",
				},
			},
			&api.SdkRule{
				Services: []string{"identity"},
				Apis:     []string{"*"},
			},
		},

		// system:user role can only access volume lifecycle commands
		"system.user": {
			&api.SdkRule{
				Services: []string{
					"volume",
					"cloudbackup",
					"credentials",
					"objectstore",
					"schedulepolicy",
					"mountattach",
					"migrate",
				},
				Apis: []string{"*"},
			},
			&api.SdkRule{
				Services: []string{"identity"},
				Apis:     []string{"*"},
			},
			&api.SdkRule{
				Services: []string{"policy"},
				Apis: []string{
					"*enumerate*",
					// This will allow system.user to view default policy also
					"*inspect*",
				},
			},
		},
	}
)

// SdkRoleManager is an implementation of the RoleManager for the SDK
type SdkRoleManager struct {
	kv kvdb.Kvdb
}

// Check interface
var _ RoleManager = &SdkRoleManager{}

// Simple function which creates key for Kvdb
func prefixWithName(name string) string {
	return rolePrefix + "/" + name
}

// Determines if the rules apply to string s
// rule can be:
// '*' - match all
// '*xxx' - ends with xxx
// 'xxx*' - starts with xxx
// '*xxx*' - contains xxx
func matchRule(rule, s string) bool {
	// no rule
	rl := len(rule)
	if rl == 0 {
		return false
	}

	// '*xxx' || 'xxx*'
	if rule[0:1] == "*" || rule[rl-1:rl] == "*" {
		// get the matching string from the rule
		match := strings.TrimSpace(strings.Join(strings.Split(rule, "*"), ""))

		// '*' or '*******'
		if len(match) == 0 {
			return true
		}

		// '*xxx*'
		if rule[0:1] == "*" && rule[rl-1:rl] == "*" {
			return strings.Contains(s, match)
		}

		// '*xxx'
		if rule[0:1] == "*" {
			return strings.HasSuffix(s, match)
		}

		// 'xxx*'
		return strings.HasPrefix(s, match)
	}

	// no wildcard stars given in rule
	return rule == s
}

// NewSdkRoleManager returns a new SDK role manager
func NewSdkRoleManager(kv kvdb.Kvdb) (*SdkRoleManager, error) {
	s := &SdkRoleManager{
		kv: kv,
	}

	// Load all default roles
	for k, v := range defaultRoles {
		role := &api.SdkRole{
			Name:  k,
			Rules: v,
		}
		if _, err := kv.Put(prefixWithName(k), role, 0); err != nil {
			return nil, err
		}
	}

	return s, nil
}

// Create saves a role in Kvdb
func (r *SdkRoleManager) Create(
	ctx context.Context,
	req *api.SdkRoleCreateRequest,
) (*api.SdkRoleCreateResponse, error) {
	if req.GetRole() == nil {
		return nil, status.Error(codes.InvalidArgument, "Must supply a role")
	} else if len(req.GetRole().GetName()) == 0 {
		return nil, status.Error(codes.InvalidArgument, "Must supply a name for the role")
	} else if err := r.validateRole(req.GetRole()); err != nil {
		return nil, err
	}

	// Determine if there is collision with default roles
	if _, ok := defaultRoles[req.GetRole().GetName()]; ok {
		return nil, status.Errorf(
			codes.InvalidArgument,
			"Name %s already used by system role", req.GetRole().GetName())
	}

	// Save value in kvdb
	_, err := r.kv.Create(prefixWithName(req.GetRole().GetName()), req.GetRole(), 0)
	if err == kvdb.ErrExist {
		// Idempotency check.
		// Check that the new rules are the same.
		oldrole, err := r.Inspect(ctx, &api.SdkRoleInspectRequest{
			Name: req.GetRole().GetName(),
		})
		if err != nil {
			return nil, err
		}
		if !reflect.DeepEqual(oldrole.GetRole(), req.GetRole()) {
			return nil, status.Error(
				codes.AlreadyExists,
				"Existing role differs from requested role")
		}
	} else if err != nil {
		return nil, status.Errorf(codes.Internal, "Failed to save role: %v", err)
	}

	return &api.SdkRoleCreateResponse{
		Role: req.GetRole(),
	}, nil
}

// Enumerate returns a list of role names
func (r *SdkRoleManager) Enumerate(
	ctx context.Context,
	req *api.SdkRoleEnumerateRequest,
) (*api.SdkRoleEnumerateResponse, error) {
	kvPairs, err := r.kv.Enumerate(rolePrefix + "/")
	if err != nil {
		return nil, status.Errorf(codes.Internal, "Failed to access roles from database: %v", err)
	}

	var names []string
	for _, kvPair := range kvPairs {
		names = append(names, strings.TrimPrefix(kvPair.Key, rolePrefix+"/"))
	}

	return &api.SdkRoleEnumerateResponse{
		Names: names,
	}, nil
}

// Inspect returns a role object
func (r *SdkRoleManager) Inspect(
	ctx context.Context,
	req *api.SdkRoleInspectRequest,
) (*api.SdkRoleInspectResponse, error) {
	if len(req.GetName()) == 0 {
		return nil, status.Error(codes.InvalidArgument, "Must supply a name for role")
	}

	elem := &api.SdkRole{}
	_, err := r.kv.GetVal(prefixWithName(req.GetName()), elem)
	if err == kvdb.ErrNotFound {
		return nil, status.Errorf(codes.NotFound, "Role %s not found", req.GetName())
	} else if err != nil {
		return nil, status.Errorf(codes.Internal, "Failed to get role %s information: %v", req.GetName(), err)
	}

	return &api.SdkRoleInspectResponse{
		Role: elem,
	}, nil
}

// Delete removes a role from Kvdb
func (r *SdkRoleManager) Delete(
	ctx context.Context,
	req *api.SdkRoleDeleteRequest,
) (*api.SdkRoleDeleteResponse, error) {
	if len(req.GetName()) == 0 {
		return nil, status.Error(codes.InvalidArgument, "Must supply a name for role")
	}

	// Determine if there is collision with default roles
	if _, ok := defaultRoles[req.GetName()]; ok {
		return nil, status.Errorf(
			codes.InvalidArgument,
			"Cannot delete system role %s", req.GetName())
	}

	_, err := r.kv.Delete(prefixWithName(req.GetName()))
	if err != kvdb.ErrNotFound && err != nil {
		return nil, status.Errorf(codes.Internal, "Failed to delete role %s: %v", req.GetName(), err)
	}

	return &api.SdkRoleDeleteResponse{}, nil
}

// Update replaces an existing role.
func (r *SdkRoleManager) Update(
	ctx context.Context,
	req *api.SdkRoleUpdateRequest,
) (*api.SdkRoleUpdateResponse, error) {
	if req.GetRole() == nil {
		return nil, status.Error(codes.InvalidArgument, "Must supply a role")
	}
	if err := r.validateRole(req.GetRole()); err != nil {
		return nil, err
	}

	// Determine if there is collision with default roles
	if _, ok := defaultRoles[req.GetRole().GetName()]; ok {
		return nil, status.Errorf(
			codes.InvalidArgument,
			"System role %s cannot be updated", req.GetRole().GetName())
	}

	_, err := r.kv.Update(prefixWithName(req.GetRole().GetName()), req.GetRole(), 0)
	if err == kvdb.ErrNotFound {
		return nil, status.Errorf(codes.NotFound, "Role %s not found", req.GetRole())
	} else if err != nil {
		return nil, status.Errorf(codes.Internal, "Failed to get role %s information: %v", req.GetRole().GetName(), err)
	}

	return &api.SdkRoleUpdateResponse{
		Role: req.GetRole(),
	}, nil
}

// Verify determines if the role has access to `fullmethod`
func (r *SdkRoleManager) Verify(ctx context.Context, roles []string, fullmethod string) error {

	// Check all roles
	for _, role := range roles {
		// Get the role rules
		resp, err := r.Inspect(ctx, &api.SdkRoleInspectRequest{
			Name: role,
		})
		if err != nil || resp == nil || resp.GetRole() == nil {
			continue
		}

		if err := r.verifyRules(resp.GetRole().GetRules(), fullmethod); err == nil {
			return nil
		}
	}

	return status.Errorf(codes.PermissionDenied, "Access denied to roles: %+s", roles)
}

// verifyRules checks if the rules authorize use of the API called `fullmethod`
func (r *SdkRoleManager) verifyRules(rules []*api.SdkRule, fullmethod string) error {
	var reqService, reqApi string

	// String: "/openstorage.api.OpenStorage<service>/<method>"
	parts := strings.Split(fullmethod, "/")

	if len(parts) > 1 {
		reqService = strings.TrimPrefix(strings.ToLower(parts[1]), "openstorage.api.openstorage")
	}

	if len(parts) > 2 {
		reqApi = strings.ToLower(parts[2])
	}

	// Go through each rule until a match is found
	for _, rule := range rules {
		for _, service := range rule.Services {
			if matchRule(service, reqService) {
				for _, api := range rule.Apis {
					if matchRule(api, reqApi) {
						return nil
					}
				}
			}
		}
	}

	return fmt.Errorf("no accessable rule to authorize access found")
}

func (r *SdkRoleManager) validateRole(role *api.SdkRole) error {
	if len(role.GetName()) == 0 {
		return status.Error(codes.InvalidArgument, "Must supply a name for role")
	} else if len(role.GetRules()) == 0 {
		return status.Error(codes.InvalidArgument, "Must supply rules for role")
	} else if strings.ContainsAny(role.GetName(), invalidChars) {
		return status.Errorf(codes.InvalidArgument, "Name cannot contain any of the following invalid characters: %s", invalidChars)
	}

	// Check if the rules have services and apis
	for _, rule := range role.GetRules() {
		if len(rule.GetServices()) == 0 {
			return status.Error(codes.InvalidArgument, "Must supply a service in rules for the role")
		} else if len(rule.GetApis()) == 0 {
			return status.Error(codes.InvalidArgument, "Must supply apis in rules for the role")
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package slinga

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestParsing(t *testing.T) {
	state := LoadPolicyFromDir("testdata/fake")

	assert.Equal(t, 2, len(state.Services), "Two services should be loaded")

	assert.Equal(t, 2, len(state.Contexts["kafka"]), "Two contexts should be loaded for kafka")
	assert.Equal(t, "kafka", state.Services["kafka"].Name, "Service name should be correct")
	assert.Equal(t, 3, len(state.Services["kafka"].Components), "Service should have components")
	assert.Equal(t, "prod", state.Contexts["kafka"][0].Name, "Context name should be correct")
	assert.Equal(t, 2, len(state.Contexts["kafka"][0].Allocations), "Context should have allocations")

	assert.Equal(t, 2, len(state.Contexts["zookeeper"]), "Two contexts should be loaded for zookeeper")
	assert.Equal(t, "zookeeper", state.Services["zookeeper"].Name, "Service name should be correct")
	assert.Equal(t, 2, len(state.Services["zookeeper"].Components), "Service should have components")
	assert.Equal(t, "prod", state.Contexts["zookeeper"][0].Name, "Context name should be correct")
	assert.Equal(t, 2, len(state.Contexts["zookeeper"][0].Allocations), "Context should have allocations")

	assert.Equal(t, "aptomi/code/fake", state.Services["zookeeper"].Components[0].Code.Type, "ZooKeeper's first component should be fake code")
	assert.Equal(t, "aptomi/code/fake", state.Services["zookeeper"].Components[1].Code.Type, "ZooKeeper's second component should be fake code")

	assert.Nil(t, state.Services["kafka"].Components[0].Code, "Kafka's first component should be service")
	assert.Equal(t, "zookeeper", state.Services["kafka"].Components[0].Service, "Kafka's first component should be service")
	assert.Equal(t, "aptomi/code/fake", state.Services["kafka"].Components[1].Code.Type, "Kafka's second component should be fake code")
	assert.Equal(t, "aptomi/code/fake", state.Services["kafka"].Components[2].Code.Type, "Kafka's third component should be fake code")
}
<commit_msg>Fix parser_test.go formatting<commit_after>package slinga

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

func TestParsing(t *testing.T) {
	state := LoadPolicyFromDir("testdata/fake")

	assert.Equal(t, 2, len(state.Services), "Two services should be loaded")

	assert.Equal(t, 2, len(state.Contexts["kafka"]), "Two contexts should be loaded for kafka")
	assert.Equal(t, "kafka", state.Services["kafka"].Name, "Service name should be correct")
	assert.Equal(t, 3, len(state.Services["kafka"].Components), "Service should have components")
	assert.Equal(t, "prod", state.Contexts["kafka"][0].Name, "Context name should be correct")
	assert.Equal(t, 2, len(state.Contexts["kafka"][0].Allocations), "Context should have allocations")

	assert.Equal(t, 2, len(state.Contexts["zookeeper"]), "Two contexts should be loaded for zookeeper")
	assert.Equal(t, "zookeeper", state.Services["zookeeper"].Name, "Service name should be correct")
	assert.Equal(t, 2, len(state.Services["zookeeper"].Components), "Service should have components")
	assert.Equal(t, "prod", state.Contexts["zookeeper"][0].Name, "Context name should be correct")
	assert.Equal(t, 2, len(state.Contexts["zookeeper"][0].Allocations), "Context should have allocations")

	assert.Equal(t, "aptomi/code/fake", state.Services["zookeeper"].Components[0].Code.Type, "ZooKeeper's first component should be fake code")
	assert.Equal(t, "aptomi/code/fake", state.Services["zookeeper"].Components[1].Code.Type, "ZooKeeper's second component should be fake code")

	assert.Nil(t, state.Services["kafka"].Components[0].Code, "Kafka's first component should be service")
	assert.Equal(t, "zookeeper", state.Services["kafka"].Components[0].Service, "Kafka's first component should be service")
	assert.Equal(t, "aptomi/code/fake", state.Services["kafka"].Components[1].Code.Type, "Kafka's second component should be fake code")
	assert.Equal(t, "aptomi/code/fake", state.Services["kafka"].Components[2].Code.Type, "Kafka's third component should be fake code")
}
<|endoftext|>