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)) } }
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 } }
// 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 }
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) } } }
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) }) } }