// CreateNamedTemporaryCredentials generates temporary credentials from permanent // credentials, valid for the given duration, starting immediately. The // temporary credentials' scopes must be a subset of the permanent credentials' // scopes. The duration may not be more than 31 days. Any authorized scopes of // the permanent credentials will be passed through as authorized scopes to the // temporary credentials, but will not be restricted via the certificate. // // See https://docs.taskcluster.net/manual/apis/temporary-credentials func (permaCreds *Credentials) CreateNamedTemporaryCredentials(tempClientID string, duration time.Duration, scopes ...string) (tempCreds *Credentials, err error) { if duration > 31*24*time.Hour { return nil, errors.New("Temporary credentials must expire within 31 days; however a duration of " + duration.String() + " was specified to (*tcclient.ConnectionData).CreateTemporaryCredentials(...) method") } now := time.Now() start := now.Add(time.Minute * -5) // subtract 5 min for clock drift expiry := now.Add(duration) if permaCreds.ClientID == "" { return nil, errors.New("Temporary credentials cannot be created from credentials that have an empty ClientId") } if permaCreds.AccessToken == "" { return nil, errors.New("Temporary credentials cannot be created from credentials that have an empty AccessToken") } if permaCreds.Certificate != "" { return nil, errors.New("Temporary credentials cannot be created from temporary credentials, only from permanent credentials") } cert := &Certificate{ Version: 1, Scopes: scopes, Start: start.UnixNano() / 1e6, Expiry: expiry.UnixNano() / 1e6, Seed: slugid.V4() + slugid.V4(), Signature: "", // gets set in updateSignature() method below } // include the issuer iff this is a named credential if tempClientID != "" { cert.Issuer = permaCreds.ClientID } cert.updateSignature(permaCreds.AccessToken, tempClientID) certBytes, err := json.Marshal(cert) if err != nil { return } tempAccessToken, err := generateTemporaryAccessToken(permaCreds.AccessToken, cert.Seed) if err != nil { return } tempCreds = &Credentials{ ClientID: permaCreds.ClientID, AccessToken: tempAccessToken, Certificate: string(certBytes), AuthorizedScopes: permaCreds.AuthorizedScopes, } if tempClientID != "" { tempCreds.ClientID = tempClientID } return }
// Test that 10000 v4 slugs are unchanged after decoding and then encoding them. func TestSlugDecodeEncode(t *testing.T) { for i := 0; i < 10000; i++ { slug1 := slugid.V4() uuid_ := slugid.Decode(slug1) slug2 := slugid.Encode(uuid_) if slug1 != slug2 { t.Errorf("Decode and encode isn't identity: '%s' != '%s'", slug1, slug2) } } }
func TestInteractivePluginShell(t *testing.T) { taskID := slugid.V4() q := &client.MockQueue{} shell := q.ExpectRedirectArtifact(taskID, 0, "private/interactive/shell.html") sockets := q.ExpectS3Artifact(taskID, 0, "private/interactive/sockets.json") plugintest.Case{ Payload: `{ "delay": 250, "function": "true", "argument": "whatever", "interactive": { "disableDisplay": true } }`, Plugin: "interactive", PluginConfig: `{}`, PluginSuccess: true, EngineSuccess: true, QueueMock: q, TaskID: taskID, AfterStarted: func(plugintest.Options) { shellToolURL := <-shell u, _ := url.Parse(shellToolURL) shellSocketURL := u.Query().Get("socketUrl") // Check that socket.json contains the socket url too var s map[string]string json.Unmarshal(<-sockets, &s) if shellSocketURL != s["shellSocketUrl"] { panic("Expected shellSocketUrl to match redirect artifact target") } debug("Opening a new shell") sh, err := shellclient.Dial(shellSocketURL, nil, false) if err != nil { panic(fmt.Sprintf("Failed to open shell, error: %s", err)) } debug("Write print-hello to shell") go func() { sh.StdinPipe().Write([]byte("print-hello")) sh.StdinPipe().Close() }() debug("Read message from shell") msg, err := ioutil.ReadAll(sh.StdoutPipe()) if err != nil { panic(fmt.Sprintf("Error reading from shell, error: %s", err)) } if string(msg) != "Hello World" { panic(fmt.Sprintf("Expected 'Hello World' got: '%s'", string(msg))) } debug("Wait for shell to terminate") result, err := sh.Wait() if err != nil { panic(fmt.Sprintf("Error from shell, error: %s", err)) } if !result { panic("Shell didn't end successfully") } }, }.Test() }
func TestInteractivePluginDisplay(t *testing.T) { taskID := slugid.V4() q := &client.MockQueue{} display := q.ExpectRedirectArtifact(taskID, 0, "private/interactive/display.html") sockets := q.ExpectS3Artifact(taskID, 0, "private/interactive/sockets.json") plugintest.Case{ Payload: `{ "delay": 250, "function": "true", "argument": "whatever", "interactive": { "disableShell": true } }`, Plugin: "interactive", PluginConfig: `{}`, PluginSuccess: true, EngineSuccess: true, QueueMock: q, TaskID: taskID, AfterStarted: func(plugintest.Options) { displayToolURL := <-display u, _ := url.Parse(displayToolURL) displaysURL := u.Query().Get("displaysUrl") socketURL := u.Query().Get("socketUrl") // Check that socket.json contains the socket url too var s map[string]string json.Unmarshal(<-sockets, &s) if socketURL != s["displaySocketUrl"] { panic("Expected displaySocketUrl to match redirect artifact target") } if displaysURL != s["displaysUrl"] { panic("Expected displaySocketUrl to match redirect artifact target") } debug("List displays") displays, err := displayclient.ListDisplays(displaysURL) if err != nil { panic(fmt.Sprintf("ListDisplays failed, error: %s", err)) } if len(displays) != 1 { panic("Expected ListDisplays to return at-least one display") } debug("OpenDisplay") d, err := displays[0].OpenDisplay() if err != nil { panic(fmt.Sprintf("Failed to OpenDisplay, error: %s", err)) } debug("Get resolution") res, err := getDisplayResolution(d) if err != nil { panic(fmt.Sprintf("Failed connect to VNC display, error: %s", err)) } // Some simple sanity tests, we can rely on the fact that resolution // doesn't change because we're testing against mock engine. if res.height != displays[0].Height { panic("height mismatch") } if res.width != displays[0].Width { panic("width mismatch") } }, }.Test() }
func TestLiveLogStreaming(t *testing.T) { taskID := slugid.V4() // Create a mock queue q := &client.MockQueue{} livelog := q.ExpectRedirectArtifact(taskID, 0, "public/logs/live.log") backing := q.ExpectS3Artifact(taskID, 0, "public/logs/live_backing.log") // Setup test case plugintest.Case{ // We use the ping-proxy payload here. This way the mock-engine won't // finish until the proxy replies 200 OK. In the proxy handler we don't // reply OK, until we've read 'Pinging' from the livelog. Hence, we ensure // that we're able to read a partial livelog. Payload: `{ "delay": 0, "function": "ping-proxy", "argument": "http://my-proxy/test" }`, Proxies: map[string]http.Handler{ "my-proxy": http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Get the livelog url u := <-livelog assert.Contains(t, u, "http", "Expected a redirect URL") // Open request to livelog req, err := http.NewRequest("GET", u, nil) require.NoError(t, err) res, err := http.DefaultClient.Do(req) require.NoError(t, err) defer res.Body.Close() // Read 'Pinging' from livelog before continuing data := []byte{} for err == nil { d := []byte{0} var n int n, err = res.Body.Read(d) if n > 0 { data = append(data, d[0]) } if strings.Contains(string(data), "Pinging") { break } } // Reply so that we can continue if strings.Contains(string(data), "Pinging") { w.WriteHeader(http.StatusOK) w.Write([]byte("[hello-world-yt5aqnur3]")) } else { w.WriteHeader(400) w.Write([]byte("Expected to see 'Pinging'")) } }), }, Plugin: "livelog", TestStruct: t, PluginSuccess: true, EngineSuccess: true, MatchLog: "[hello-world-yt5aqnur3]", TaskID: taskID, QueueMock: q, AfterFinished: func(plugintest.Options) { assert.Contains(t, <-livelog, taskID, "Expected an artifact URL containing the taskId") assert.Contains(t, string(<-backing), "[hello-world-yt5aqnur3]") }, }.Test() }