@metaplay/metaplay-auth 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +0 -42
- package/dist/index.js +91 -46
- package/dist/index.js.map +1 -1
- package/dist/src/auth.js +27 -1
- package/dist/src/auth.js.map +1 -1
- package/dist/src/deployment.js +25 -12
- package/dist/src/deployment.js.map +1 -1
- package/dist/src/stackapi.js +147 -16
- package/dist/src/stackapi.js.map +1 -1
- package/index.ts +89 -57
- package/package.json +16 -14
- package/src/auth.ts +32 -1
- package/src/deployment.ts +26 -12
- package/src/stackapi.ts +209 -18
package/src/stackapi.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { isValidFQDN, getGameserverAdminUrl } from './utils.js'
|
|
2
2
|
import { logger } from './logging.js'
|
|
3
|
+
import { dump } from 'js-yaml'
|
|
4
|
+
import { getUserinfo } from './auth.js'
|
|
3
5
|
|
|
4
6
|
interface AwsCredentialsResponse {
|
|
5
7
|
AccessKeyId: string
|
|
@@ -8,34 +10,84 @@ interface AwsCredentialsResponse {
|
|
|
8
10
|
Expiration: string
|
|
9
11
|
}
|
|
10
12
|
|
|
11
|
-
interface GameserverId {
|
|
13
|
+
export interface GameserverId {
|
|
12
14
|
gameserver?: string
|
|
13
15
|
organization?: string
|
|
14
16
|
project?: string
|
|
15
17
|
environment?: string
|
|
16
18
|
}
|
|
17
19
|
|
|
20
|
+
interface KubeConfig {
|
|
21
|
+
apiVersion?: string
|
|
22
|
+
kind: string
|
|
23
|
+
'current-context': string
|
|
24
|
+
clusters: KubeConfigCluster[]
|
|
25
|
+
contexts: KubeConfigContext[]
|
|
26
|
+
users: KubeConfigUser[]
|
|
27
|
+
preferences?: any
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface KubeConfigCluster {
|
|
31
|
+
cluster: {
|
|
32
|
+
'certificate-authority-data': string
|
|
33
|
+
server: string
|
|
34
|
+
}
|
|
35
|
+
name: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface KubeConfigContext {
|
|
39
|
+
context: {
|
|
40
|
+
cluster: string
|
|
41
|
+
user: string
|
|
42
|
+
namespace?: string
|
|
43
|
+
}
|
|
44
|
+
name: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface KubeConfigUser {
|
|
48
|
+
name: string
|
|
49
|
+
user: {
|
|
50
|
+
token?: string
|
|
51
|
+
exec?: {
|
|
52
|
+
command: string
|
|
53
|
+
args: string[]
|
|
54
|
+
apiVersion: string
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface KubeExecCredential {
|
|
60
|
+
apiVersion: string
|
|
61
|
+
kind: string
|
|
62
|
+
spec: {
|
|
63
|
+
cluster?: {
|
|
64
|
+
server: string
|
|
65
|
+
certificateAuthorityData: string
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
status: {
|
|
69
|
+
token: string
|
|
70
|
+
expirationTimestamp: string
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
18
74
|
export class StackAPI {
|
|
19
75
|
private readonly accessToken: string
|
|
20
76
|
|
|
21
|
-
private
|
|
77
|
+
private readonly _stackApiBaseUrl: string
|
|
22
78
|
|
|
23
|
-
constructor (accessToken: string) {
|
|
79
|
+
constructor (accessToken: string, stackApiBaseUrl: string | undefined) {
|
|
24
80
|
if (accessToken == null) {
|
|
25
81
|
throw new Error('accessToken must be provided')
|
|
26
82
|
}
|
|
27
83
|
|
|
28
84
|
this.accessToken = accessToken
|
|
29
|
-
this._stack_api_base_uri = 'https://infra.p1.metaplay.io/stackapi'
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
get stack_api_base_uri (): string {
|
|
33
|
-
return this._stack_api_base_uri.replace(/\/$/, '')
|
|
34
|
-
}
|
|
35
85
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
86
|
+
if (stackApiBaseUrl) {
|
|
87
|
+
this._stackApiBaseUrl = stackApiBaseUrl.replace(/\/$/, '') // Remove trailing slash
|
|
88
|
+
} else {
|
|
89
|
+
this._stackApiBaseUrl = 'https://infra.p1.metaplay.io/stackapi'
|
|
90
|
+
}
|
|
39
91
|
}
|
|
40
92
|
|
|
41
93
|
async getAwsCredentials (gs: GameserverId): Promise<AwsCredentialsResponse> {
|
|
@@ -45,10 +97,10 @@ export class StackAPI {
|
|
|
45
97
|
const adminUrl = getGameserverAdminUrl(gs.gameserver)
|
|
46
98
|
url = `https://${adminUrl}/.infra/credentials/aws`
|
|
47
99
|
} else {
|
|
48
|
-
url = `${this.
|
|
100
|
+
url = `${this._stackApiBaseUrl}/v0/credentials/${gs.gameserver}/aws`
|
|
49
101
|
}
|
|
50
102
|
} else if (gs.organization != null && gs.project != null && gs.environment != null) {
|
|
51
|
-
url = `${this.
|
|
103
|
+
url = `${this._stackApiBaseUrl}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}/credentials/aws`
|
|
52
104
|
} else {
|
|
53
105
|
throw new Error('Invalid arguments for getAwsCredentials')
|
|
54
106
|
}
|
|
@@ -76,10 +128,10 @@ export class StackAPI {
|
|
|
76
128
|
const adminUrl = getGameserverAdminUrl(gs.gameserver)
|
|
77
129
|
url = `https://${adminUrl}/.infra/credentials/k8s`
|
|
78
130
|
} else {
|
|
79
|
-
url = `${this.
|
|
131
|
+
url = `${this._stackApiBaseUrl}/v0/credentials/${gs.gameserver}/k8s`
|
|
80
132
|
}
|
|
81
133
|
} else if (gs.organization != null && gs.project != null && gs.environment != null) {
|
|
82
|
-
url = `${this.
|
|
134
|
+
url = `${this._stackApiBaseUrl}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}/credentials/k8s`
|
|
83
135
|
} else {
|
|
84
136
|
throw new Error('Invalid arguments for getKubeConfig')
|
|
85
137
|
}
|
|
@@ -101,6 +153,145 @@ export class StackAPI {
|
|
|
101
153
|
return await response.text()
|
|
102
154
|
}
|
|
103
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Get a `kubeconfig` payload which invokes `metaplay-auth get-kubernetes-execcredential` to get the actual
|
|
158
|
+
* access credentials each time the kubeconfig is used.
|
|
159
|
+
* @param gs Game server environment to get credentials for.
|
|
160
|
+
* @returns The kubeconfig YAML.
|
|
161
|
+
*/
|
|
162
|
+
async getKubeConfigExecCredential (gs: GameserverId): Promise<string> {
|
|
163
|
+
let url = ''
|
|
164
|
+
let gsSlug = ''
|
|
165
|
+
const execArgs = ['get-kubernetes-execcredential']
|
|
166
|
+
if (gs.gameserver != null) {
|
|
167
|
+
const adminUrl = getGameserverAdminUrl(gs.gameserver)
|
|
168
|
+
url = `https://${adminUrl}/.infra/credentials/k8s?type=execcredential`
|
|
169
|
+
gsSlug = gs.gameserver
|
|
170
|
+
execArgs.push('--gameserver', gs.gameserver)
|
|
171
|
+
} else if (gs.organization != null && gs.project != null && gs.environment != null) {
|
|
172
|
+
url = `${this._stackApiBaseUrl}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}/credentials/k8s?type=execcredential`
|
|
173
|
+
gsSlug = `${gs.organization}.${gs.project}.${gs.environment}`
|
|
174
|
+
execArgs.push('--organization', gs.organization, '--project', gs.project, '--environment', gs.environment)
|
|
175
|
+
} else {
|
|
176
|
+
throw new Error('Invalid arguments for getKubeConfigExecCredential')
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
logger.debug(`Getting Kubernetes KubeConfig from ${url}...`)
|
|
180
|
+
|
|
181
|
+
const response = await fetch(url, {
|
|
182
|
+
method: 'POST',
|
|
183
|
+
headers: {
|
|
184
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
185
|
+
'Content-Type': 'application/json'
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
if (response.status !== 200) {
|
|
190
|
+
throw new Error(`Failed to fetch Kubernetes KubeConfig: ${response.statusText}`)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// get the execcredential and morph it into a kubeconfig which calls metaplay-auth for the token
|
|
194
|
+
let kubeExecCredential
|
|
195
|
+
try {
|
|
196
|
+
kubeExecCredential = await response.json() as KubeExecCredential
|
|
197
|
+
} catch {
|
|
198
|
+
throw new Error('Failed to fetch Kubernetes KubeConfig: the server response is not JSON')
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!kubeExecCredential.spec.cluster) {
|
|
202
|
+
throw new Error('Received kubeExecCredential with missing spec.cluster')
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let namespace = 'default'
|
|
206
|
+
let user = 'user'
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
const environment = await this.getEnvironmentDetails(gs)
|
|
210
|
+
namespace = environment.deployment?.kubernetes_namespace ?? namespace
|
|
211
|
+
} catch (e) {
|
|
212
|
+
logger.debug('Failed to get environment details, using defaults', e)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
const userinfo = await getUserinfo(this.accessToken)
|
|
217
|
+
user = userinfo.sub ?? user
|
|
218
|
+
} catch (e) {
|
|
219
|
+
logger.debug('Failed to get userinfo, using defaults', e)
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const kubeConfig: KubeConfig = {
|
|
223
|
+
apiVersion: 'v1',
|
|
224
|
+
kind: 'Config',
|
|
225
|
+
'current-context': gsSlug,
|
|
226
|
+
clusters: [
|
|
227
|
+
{
|
|
228
|
+
cluster: {
|
|
229
|
+
'certificate-authority-data': kubeExecCredential.spec.cluster.certificateAuthorityData,
|
|
230
|
+
server: kubeExecCredential.spec.cluster.server
|
|
231
|
+
},
|
|
232
|
+
name: kubeExecCredential.spec.cluster.server
|
|
233
|
+
}
|
|
234
|
+
],
|
|
235
|
+
contexts: [
|
|
236
|
+
{
|
|
237
|
+
context: {
|
|
238
|
+
cluster: kubeExecCredential.spec.cluster.server,
|
|
239
|
+
namespace,
|
|
240
|
+
user
|
|
241
|
+
},
|
|
242
|
+
name: gsSlug,
|
|
243
|
+
}
|
|
244
|
+
],
|
|
245
|
+
users: [
|
|
246
|
+
{
|
|
247
|
+
name: user,
|
|
248
|
+
user: {
|
|
249
|
+
exec: {
|
|
250
|
+
apiVersion: 'client.authentication.k8s.io/v1beta1',
|
|
251
|
+
command: 'metaplay-auth', // todo: figure out how to refer to metaplay-auth itself
|
|
252
|
+
args: execArgs,
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
],
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// return as yaml for easier consumption
|
|
260
|
+
return dump(kubeConfig)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async getKubeExecCredential (gs: GameserverId): Promise<string> {
|
|
264
|
+
let url = ''
|
|
265
|
+
if (gs.gameserver != null) {
|
|
266
|
+
if (isValidFQDN(gs.gameserver)) {
|
|
267
|
+
const adminUrl = getGameserverAdminUrl(gs.gameserver)
|
|
268
|
+
url = `https://${adminUrl}/.infra/credentials/k8s?type=execcredential`
|
|
269
|
+
} else {
|
|
270
|
+
url = `${this._stackApiBaseUrl}/v0/credentials/${gs.gameserver}/k8s?type=execcredential`
|
|
271
|
+
}
|
|
272
|
+
} else if (gs.organization != null && gs.project != null && gs.environment != null) {
|
|
273
|
+
url = `${this._stackApiBaseUrl}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}/credentials/k8s?type=execcredential`
|
|
274
|
+
} else {
|
|
275
|
+
throw new Error('Invalid arguments for getKubeConfig')
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
logger.debug(`Getting Kubernetes ExecCredential from ${url}...`)
|
|
279
|
+
|
|
280
|
+
const response = await fetch(url, {
|
|
281
|
+
method: 'POST',
|
|
282
|
+
headers: {
|
|
283
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
284
|
+
'Content-Type': 'application/json'
|
|
285
|
+
}
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
if (response.status !== 200) {
|
|
289
|
+
throw new Error(`Failed to fetch Kubernetes ExecCredential: ${response.statusText}`)
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return await response.text()
|
|
293
|
+
}
|
|
294
|
+
|
|
104
295
|
async getEnvironmentDetails (gs: GameserverId): Promise<any> {
|
|
105
296
|
let url = ''
|
|
106
297
|
if (gs.gameserver != null) {
|
|
@@ -108,10 +299,10 @@ export class StackAPI {
|
|
|
108
299
|
const adminUrl = getGameserverAdminUrl(gs.gameserver)
|
|
109
300
|
url = `https://${adminUrl}/.infra/environment`
|
|
110
301
|
} else {
|
|
111
|
-
url = `${this.
|
|
302
|
+
url = `${this._stackApiBaseUrl}/v0/deployments/${gs.gameserver}`
|
|
112
303
|
}
|
|
113
304
|
} else if (gs.organization != null && gs.project != null && gs.environment != null) {
|
|
114
|
-
url = `${this.
|
|
305
|
+
url = `${this._stackApiBaseUrl}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}`
|
|
115
306
|
} else {
|
|
116
307
|
throw new Error('Invalid arguments for environment details')
|
|
117
308
|
}
|