Skip to content

Commit 8213521

Browse files
committed
incident.io Teleport Access Request Plugin
- An integration to allow users to request access via Teleport and have that create alerts in incident.io, and auto approve users that are on schedules specified via annotations
1 parent 3c38b4a commit 8213521

File tree

14 files changed

+2247
-0
lines changed

14 files changed

+2247
-0
lines changed

api/types/plugin.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ const (
7272
PluginTypeEntraID = "entra-id"
7373
// PluginTypeSCIM indicates a generic SCIM integration
7474
PluginTypeSCIM = "scim"
75+
// PluginTypeIncidentio is the incident.io access request plugin
76+
PluginTypeIncidentio = "incidentio"
7577
)
7678

7779
// PluginSubkind represents the type of the plugin, e.g., access request, MDM etc.
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Teleport
3+
* Copyright (C) 2023 Gravitational, Inc.
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
package incidentio
20+
21+
import (
22+
"context"
23+
"encoding/json"
24+
"io"
25+
"log"
26+
"net/http"
27+
"net/http/httptest"
28+
"testing"
29+
30+
"github.com/gravitational/trace"
31+
"github.com/stretchr/testify/assert"
32+
33+
"github.com/gravitational/teleport/api/types"
34+
)
35+
36+
func TestCreateAlert(t *testing.T) {
37+
recievedReq := ""
38+
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
39+
bodyBytes, err := io.ReadAll(req.Body)
40+
if err != nil {
41+
log.Fatal(err)
42+
}
43+
recievedReq = string(bodyBytes)
44+
}))
45+
defer func() { testServer.Close() }()
46+
47+
c, err := NewAlertClient(ClientConfig{
48+
AlertSourceEndpoint: testServer.URL,
49+
ClusterName: "someClusterName",
50+
})
51+
assert.NoError(t, err)
52+
53+
_, err = c.CreateAlert(context.Background(), "someRequestID", RequestData{
54+
User: "someUser",
55+
Roles: []string{"role1", "role2"},
56+
RequestReason: "someReason",
57+
SystemAnnotations: types.Labels{
58+
types.TeleportNamespace + types.ReqAnnotationNotifySchedulesLabel: {"[email protected]", "bb4d9938-c3c2-455d-aaab-727aa701c0d8"},
59+
},
60+
})
61+
assert.NoError(t, err)
62+
63+
expected := AlertBody{
64+
Title: "Access request from someUser",
65+
DeduplicationKey: "teleport-access-request/someRequestID",
66+
Description: "Access request from someUser",
67+
Status: "firing",
68+
Metadata: map[string]string{
69+
"request_id": "someRequestID",
70+
},
71+
}
72+
var got AlertBody
73+
err = json.Unmarshal([]byte(recievedReq), &got)
74+
assert.NoError(t, err)
75+
76+
assert.Equal(t, expected, got)
77+
}
78+
79+
func TestResolveAlert(t *testing.T) {
80+
recievedReq := ""
81+
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
82+
bodyBytes, err := io.ReadAll(req.Body)
83+
if err != nil {
84+
log.Fatal(err)
85+
}
86+
recievedReq = string(bodyBytes)
87+
}))
88+
defer func() { testServer.Close() }()
89+
90+
c, err := NewAlertClient(ClientConfig{
91+
AlertSourceEndpoint: testServer.URL,
92+
ClusterName: "someClusterName",
93+
})
94+
assert.NoError(t, err)
95+
96+
err = c.ResolveAlert(context.Background(), "someAlertID", Resolution{
97+
Tag: ResolvedApproved,
98+
Reason: "someReason",
99+
})
100+
101+
assert.NoError(t, err)
102+
103+
assert.Equal(t, `{"message":"Access request resolved: approved","description":"Access request has been approved","deduplication_key":"teleport-access-request/someAlertID","status":"resolved","metadata":{"request_id":"someAlertID"}}`, recievedReq)
104+
}
105+
106+
func TestCreateAlertError(t *testing.T) {
107+
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
108+
res.WriteHeader(http.StatusForbidden)
109+
}))
110+
defer func() { testServer.Close() }()
111+
112+
c, err := NewAlertClient(ClientConfig{
113+
AlertSourceEndpoint: testServer.URL,
114+
})
115+
assert.NoError(t, err)
116+
117+
_, err = c.CreateAlert(context.Background(), "someRequestID", RequestData{})
118+
assert.True(t, trace.IsAccessDenied(err))
119+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Teleport
3+
* Copyright (C) 2023 Gravitational, Inc.
4+
*
5+
* This program is free software: you can redistribute it and/or modify
6+
* it under the terms of the GNU Affero General Public License as published by
7+
* the Free Software Foundation, either version 3 of the License, or
8+
* (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
* GNU Affero General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Affero General Public License
16+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
package incidentio
20+
21+
import (
22+
"context"
23+
"net/http"
24+
"net/http/httptest"
25+
"testing"
26+
27+
"github.com/stretchr/testify/assert"
28+
)
29+
30+
func TestGetSchedule(t *testing.T) {
31+
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
32+
if req.URL.Path == "/v2/schedules/someRequestID" {
33+
res.WriteHeader(http.StatusOK)
34+
} else {
35+
res.WriteHeader(http.StatusBadRequest)
36+
}
37+
}))
38+
defer func() { testServer.Close() }()
39+
40+
c, err := NewAPIClient(ClientConfig{
41+
APIEndpoint: testServer.URL,
42+
APIKey: "someAPIKey",
43+
ClusterName: "someClusterName",
44+
})
45+
assert.NoError(t, err)
46+
47+
_, err = c.GetOnCall(context.Background(), "someRequestID")
48+
assert.NoError(t, err)
49+
}
50+
51+
func TestHealthCheck(t *testing.T) {
52+
testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
53+
if req.URL.Path == "/v2/schedules" {
54+
res.WriteHeader(http.StatusOK)
55+
} else {
56+
res.WriteHeader(http.StatusBadRequest)
57+
}
58+
}))
59+
defer func() { testServer.Close() }()
60+
61+
c, err := NewAPIClient(ClientConfig{
62+
APIEndpoint: testServer.URL,
63+
APIKey: "someAPIKey",
64+
ClusterName: "someClusterName",
65+
})
66+
assert.NoError(t, err)
67+
68+
err = c.CheckHealth(context.Background())
69+
assert.NoError(t, err)
70+
}

0 commit comments

Comments
 (0)