Beispiel #1
0
func TestSessionCacheParallel(t *testing.T) {
	var storage apitest.TrxStorage
	var auth = apitest.NewFakeAuth()

	cache := api.NewCache(auth.Auth)
	cache.Storage = &storage

	const ConcurrentAuths = 10

	var wg sync.WaitGroup
	wg.Add(ConcurrentAuths)
	for i := 0; i < ConcurrentAuths; i++ {
		go func(i int) {
			defer wg.Done()
			cache.Auth(&api.AuthOptions{
				User: &api.User{
					Username: "******" + strconv.Itoa(i),
					Team:     "foobar" + strconv.Itoa(i),
				},
			})
		}(i)
	}

	wg.Wait()

	if sessions := storage.Build(); !reflect.DeepEqual(sessions, auth.Sessions) {
		t.Fatalf("got %+v, want %+v", sessions, auth.Sessions)
	}

	if len(auth.Sessions) != ConcurrentAuths {
		t.Fatalf("want len(auth)=%d; got: %d", ConcurrentAuths, len(auth.Sessions))
	}
}
Beispiel #2
0
func (ka *KloudAuth) initAuth() {
	if ka.Storage != nil {
		cache := api.NewCache(ka.rpcAuth)
		cache.Storage = ka.Storage
		ka.auth = cache.Auth
	} else {
		ka.auth = ka.rpcAuth
	}
}
Beispiel #3
0
// New gives new, registered kloud kite.
//
// If conf contains invalid or missing configuration, it return non-nil error.
func New(conf *Config) (*Kloud, error) {
	k := kite.New(stack.NAME, stack.VERSION)
	k.Config = kiteconfig.MustGet()
	k.Config.Port = conf.Port

	k.ClientFunc = httputil.ClientFunc(conf.DebugMode)

	if conf.DebugMode {
		k.SetLogLevel(kite.DEBUG)
	}

	if conf.Region != "" {
		k.Config.Region = conf.Region
	}

	if conf.Environment != "" {
		k.Config.Environment = conf.Environment
	}

	// TODO(rjeczalik): refactor modelhelper methods to not use global DB
	modelhelper.Initialize(conf.MongoURL)

	sess, err := newSession(conf, k)
	if err != nil {
		return nil, err
	}

	e := newEndpoints(conf)

	sess.Log.Debug("Konfig.Endpoints: %s", util.LazyJSON(e))

	authUsers := map[string]string{
		"kloudctl": conf.KloudSecretKey,
	}

	restClient := httputil.DefaultRestClient(conf.DebugMode)

	storeOpts := &credential.Options{
		MongoDB: sess.DB,
		Log:     sess.Log.New("stackcred"),
		Client:  restClient,
	}

	if !conf.NoSneaker {
		storeOpts.CredURL = e.Social().WithPath("/credential").Private.URL
	}

	sess.Log.Debug("storeOpts: %+v", storeOpts)

	userPrivateKey, userPublicKey := userMachinesKeys(conf.UserPublicKey, conf.UserPrivateKey)

	stacker := &provider.Stacker{
		DB:             sess.DB,
		Log:            sess.Log,
		Kite:           sess.Kite,
		Userdata:       sess.Userdata,
		Debug:          conf.DebugMode,
		KloudSecretKey: conf.KloudSecretKey,
		CredStore:      credential.NewStore(storeOpts),
		TunnelURL:      conf.TunnelURL,
		SSHKey: &publickeys.Keys{
			KeyName:    publickeys.DeployKeyName,
			PrivateKey: userPrivateKey,
			PublicKey:  userPublicKey,
		},
	}

	stats := common.MustInitMetrics(Name)

	kloud := &Kloud{
		Kite:  k,
		Stack: stack.New(),
		Queue: &queue.Queue{
			Interval: 5 * time.Second,
			Log:      sess.Log.New("queue"),
			Kite:     k,
			MongoDB:  sess.DB,
		},
	}

	authFn := func(opts *api.AuthOptions) (*api.Session, error) {
		s, err := modelhelper.FetchOrCreateSession(opts.User.Username, opts.User.Team)
		if err != nil {
			return nil, err
		}

		return &api.Session{
			ClientID: s.ClientId,
			User: &api.User{
				Username: s.Username,
				Team:     s.GroupName,
			},
		}, nil
	}

	transport := &api.Transport{
		RoundTripper: storeOpts.Client.Transport,
		AuthFunc:     api.NewCache(authFn).Auth,
		Debug:        conf.DebugMode,
	}

	if conf.DebugMode {
		transport.Log = sess.Log
	}

	kloud.Stack.Endpoints = e
	kloud.Stack.Userdata = sess.Userdata
	kloud.Stack.DescribeFunc = provider.Desc
	kloud.Stack.CredClient = credential.NewClient(storeOpts)
	kloud.Stack.MachineClient = machine.NewClient(machine.NewMongoDatabase())
	kloud.Stack.TeamClient = team.NewClient(team.NewMongoDatabase())
	kloud.Stack.PresenceClient = client.NewInternal(e.Social().Private.String())
	kloud.Stack.PresenceClient.HTTPClient = restClient
	kloud.Stack.RemoteClient = &remoteapi.Client{
		Client:    storeOpts.Client,
		Transport: transport,
		Endpoint:  e.Koding.Private.URL,
	}

	kloud.Stack.ContextCreator = func(ctx context.Context) context.Context {
		return session.NewContext(ctx, sess)
	}

	kloud.Stack.Metrics = stats

	// RSA key pair that we add to the newly created machine for
	// provisioning.
	kloud.Stack.PublicKeys = stacker.SSHKey
	kloud.Stack.DomainStorage = sess.DNSStorage
	kloud.Stack.Domainer = sess.DNSClient
	kloud.Stack.Locker = stacker
	kloud.Stack.Log = sess.Log
	kloud.Stack.SecretKey = conf.KloudSecretKey

	for _, p := range provider.All() {
		s := stacker.New(p)

		if err = kloud.Stack.AddProvider(p.Name, s); err != nil {
			return nil, err
		}

		kloud.Queue.Register(s)

		sess.Log.Debug("registering %q provider", p.Name)
	}

	go kloud.Queue.Run()

	if conf.KeygenAccessKey != "" && conf.KeygenSecretKey != "" {
		cfg := &keygen.Config{
			AccessKey:  conf.KeygenAccessKey,
			SecretKey:  conf.KeygenSecretKey,
			Region:     conf.KeygenRegion,
			Bucket:     conf.KeygenBucket,
			AuthExpire: conf.KeygenTokenTTL,
			AuthFunc:   kloud.Stack.ValidateUser,
			Kite:       k,
		}

		kloud.Keygen = keygen.NewServer(cfg)
	} else {
		k.Log.Warning(`disabling "keygen" methods due to missing S3/STS credentials`)
	}

	// Teams/stack handling methods.
	k.HandleFunc("plan", kloud.Stack.Plan)
	k.HandleFunc("apply", kloud.Stack.Apply)
	k.HandleFunc("describeStack", kloud.Stack.Status)
	k.HandleFunc("authenticate", kloud.Stack.Authenticate)
	k.HandleFunc("bootstrap", kloud.Stack.Bootstrap)
	k.HandleFunc("import", kloud.Stack.Import)

	// Credential handling.
	k.HandleFunc("credential.describe", kloud.Stack.CredentialDescribe)
	k.HandleFunc("credential.list", kloud.Stack.CredentialList)
	k.HandleFunc("credential.add", kloud.Stack.CredentialAdd)

	// Authorization handling.
	k.HandleFunc("auth.login", kloud.Stack.AuthLogin)
	k.HandleFunc("auth.passwordLogin", kloud.Stack.AuthPasswordLogin).DisableAuthentication()

	// Configuration handling.
	k.HandleFunc("config.metadata", kloud.Stack.ConfigMetadata)

	// Team handling.
	k.HandleFunc("team.list", kloud.Stack.TeamList)
	k.HandleFunc("team.whoami", kloud.Stack.TeamWhoami)

	// Machine handling.
	k.HandleFunc("machine.list", kloud.Stack.MachineList)

	// Single machine handling.
	k.HandleFunc("stop", kloud.Stack.Stop)
	k.HandleFunc("start", kloud.Stack.Start)
	k.HandleFunc("info", kloud.Stack.Info)
	k.HandleFunc("event", kloud.Stack.Event)

	// Klient proxy methods.
	k.HandleFunc("admin.add", kloud.Stack.AdminAdd)
	k.HandleFunc("admin.remove", kloud.Stack.AdminRemove)

	k.HandleHTTPFunc("/healthCheck", artifact.HealthCheckHandler(Name))
	k.HandleHTTPFunc("/version", artifact.VersionHandler())

	for worker, key := range authUsers {
		worker, key := worker, key
		k.Authenticators[worker] = func(r *kite.Request) error {
			if r.Auth.Key != key {
				return errors.New("wrong secret key passed, you are not authenticated")
			}
			return nil
		}
	}

	if conf.DebugMode {
		// This should be actually debug level 2. It outputs every single Kite
		// message and enables the kite debugging system. So enable it only if
		// you need it.
		// k.SetLogLevel(kite.DEBUG)
		k.Log.Info("Debug mode enabled")
	}

	if conf.TestMode {
		k.Log.Info("Test mode enabled")
	}

	registerURL := k.RegisterURL(!conf.Public)
	if conf.RegisterURL != "" {
		u, err := url.Parse(conf.RegisterURL)
		if err != nil {
			return nil, fmt.Errorf("Couldn't parse register url: %s", err)
		}

		registerURL = u
	}

	if err := k.RegisterForever(registerURL); err != nil {
		return nil, err
	}

	return kloud, nil
}
Beispiel #4
0
func TestSessionCache(t *testing.T) {
	var storage apitest.TrxStorage
	var auth = apitest.NewFakeAuth()

	users := []*api.Session{
		{User: &api.User{Username: "******", Team: "foobar"}},
		{User: &api.User{Username: "******", Team: "foobar"}},
		{User: &api.User{Username: "******", Team: "foobar"}},
		{User: &api.User{Username: "******", Team: "team"}},
	}

	usernames := make(map[string]struct{}, len(users))
	for _, user := range users {
		usernames[user.User.String()] = struct{}{}
	}

	cases := []struct {
		name string
		opts *api.AuthOptions // client
		trxs []apitest.Trx    // underlying cache operations
	}{{
		"new user1",
		&api.AuthOptions{
			User: users[0].User,
		},
		[]apitest.Trx{
			{Type: "get", Session: users[0]},
			{Type: "set", Session: users[0]},
		},
	}, {
		"already cached user",
		&api.AuthOptions{
			User: users[0].User,
		},
		[]apitest.Trx{
			{Type: "get", Session: users[0]},
		},
	}, {
		"invalide session of a cached user",
		&api.AuthOptions{
			User:    users[0].User,
			Refresh: true,
		},
		[]apitest.Trx{
			{Type: "delete", Session: users[0]},
			{Type: "set", Session: users[0]},
		},
	}, {
		"new user2",
		&api.AuthOptions{
			User: users[1].User,
		},
		[]apitest.Trx{
			{Type: "get", Session: users[1]},
			{Type: "set", Session: users[1]},
		},
	}, {
		"new user3",
		&api.AuthOptions{
			User: users[2].User,
		},
		[]apitest.Trx{
			{Type: "get", Session: users[2]},
			{Type: "set", Session: users[2]},
		},
	}, {
		"new user",
		&api.AuthOptions{
			User: users[3].User,
		},
		[]apitest.Trx{
			{Type: "get", Session: users[3]},
			{Type: "set", Session: users[3]},
		},
	}}

	cache := api.NewCache(auth.Auth)
	cache.Storage = &storage

	for _, cas := range cases {
		t.Run(cas.name, func(t *testing.T) {
			trxID := len(storage.Trxs)

			_, err := cache.Auth(cas.opts)
			if err != nil {
				t.Fatalf("Auth()=%s", err)
			}

			if err := storage.Slice(trxID).Match(cas.trxs); err != nil {
				t.Fatalf("Match()=%s", err)
			}
		})
	}

	if sessions := storage.Build(); !reflect.DeepEqual(sessions, auth.Sessions) {
		t.Fatalf("got %+v, want %+v", sessions, auth.Sessions)
	}

	if len(auth.Sessions) != len(usernames) {
		t.Fatalf("want len(auth)=%d == len(allKeys)=%d", len(auth.Sessions), len(usernames))
	}

	for username := range usernames {
		if _, ok := auth.Sessions[username]; !ok {
			t.Fatalf("username %q not found in auth", username)
		}
	}
}
Beispiel #5
0
func TestTransport(t *testing.T) {
	var auth = apitest.NewFakeAuth()

	s, err := discovertest.NewServer(http.HandlerFunc(auth.GetSession))
	if err != nil {
		t.Fatalf("NewServer()=%s", err)
	}
	defer s.Close()

	cache := api.NewCache(auth.Auth)

	users := []*api.Session{
		{User: &api.User{Username: "******", Team: "foobar"}},
		{User: &api.User{Username: "******", Team: "foobar"}},
		{User: &api.User{Username: "******", Team: "foobar"}},
		{User: &api.User{Username: "******", Team: "team"}},
	}

	cases := []struct {
		name  string
		sess  *api.Session // client
		errs  []error      // fake errors for endpoint
		codes []int        // fake response codes for endpoint
		err   error        // final error from client
	}{{
		"new user1",
		users[0],
		nil, nil, nil,
	}, {
		"cached user1",
		users[0],
		nil, nil, nil,
	}, {
		"new user2",
		users[1],
		nil, nil, nil,
	}, {
		"new user3 with an error",
		users[2],
		[]error{&net.DNSError{IsTemporary: true}}, nil, nil,
	}, {
		"cached user3 with an error and response codes",
		users[2],
		[]error{&net.DNSError{IsTemporary: true}}, []int{401, 401}, nil,
	}}

	rec := &apitest.AuthRecorder{
		AuthFunc: cache.Auth,
	}

	client := http.Client{
		Transport: &api.Transport{
			RoundTripper: &apitest.FakeTransport{},
			AuthFunc:     rec.Auth,
		},
	}

	for _, cas := range cases {
		t.Run(cas.name, func(t *testing.T) {
			rec.Reset()

			req, err := http.NewRequest("POST", s.URL, strings.NewReader(cas.name))
			if err != nil {
				t.Fatalf("NewRequest()=%s", err)
			}

			req = cas.sess.User.WithRequest(req)

			if len(cas.errs) != 0 {
				req = apitest.WithErrors(req, cas.errs...)
			}

			if len(cas.codes) != 0 {
				req = apitest.WithResponseCodes(req, cas.codes...)
			}

			resp, err := client.Do(req)
			if err != nil {
				t.Fatalf("Do()=%s", err)
			}

			if want, got := 1+len(cas.errs)+len(cas.codes), len(rec.Options); got != want {
				t.Fatalf("want %d, got %d: %+v", got, want, rec.Options)
			}

			if resp.StatusCode != http.StatusOK {
				t.Fatalf("got %q, want %q", http.StatusText(resp.StatusCode), http.StatusText(http.StatusOK))
			}

			var other api.Session

			if err := json.NewDecoder(resp.Body).Decode(&other); err != nil {
				t.Fatalf("Decode()=%s", err)
			}

			if err := other.Valid(); err != nil {
				t.Fatalf("Valid()=%s", err)
			}

			if err := other.Match(cas.sess); err != nil {
				t.Fatalf("Match()=%s", err)
			}

			api.Log.Info("received session: %#v", &other)
		})
	}
}