@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/dist/src/stackapi.js
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
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
|
export class StackAPI {
|
|
4
6
|
accessToken;
|
|
5
|
-
|
|
6
|
-
constructor(accessToken) {
|
|
7
|
+
_stackApiBaseUrl;
|
|
8
|
+
constructor(accessToken, stackApiBaseUrl) {
|
|
7
9
|
if (accessToken == null) {
|
|
8
10
|
throw new Error('accessToken must be provided');
|
|
9
11
|
}
|
|
10
12
|
this.accessToken = accessToken;
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
uri = uri.replace(/\/$/, ''); // Remove trailing slash
|
|
18
|
-
this._stack_api_base_uri = uri;
|
|
13
|
+
if (stackApiBaseUrl) {
|
|
14
|
+
this._stackApiBaseUrl = stackApiBaseUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
this._stackApiBaseUrl = 'https://infra.p1.metaplay.io/stackapi';
|
|
18
|
+
}
|
|
19
19
|
}
|
|
20
20
|
async getAwsCredentials(gs) {
|
|
21
21
|
let url = '';
|
|
@@ -25,11 +25,11 @@ export class StackAPI {
|
|
|
25
25
|
url = `https://${adminUrl}/.infra/credentials/aws`;
|
|
26
26
|
}
|
|
27
27
|
else {
|
|
28
|
-
url = `${this.
|
|
28
|
+
url = `${this._stackApiBaseUrl}/v0/credentials/${gs.gameserver}/aws`;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
else if (gs.organization != null && gs.project != null && gs.environment != null) {
|
|
32
|
-
url = `${this.
|
|
32
|
+
url = `${this._stackApiBaseUrl}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}/credentials/aws`;
|
|
33
33
|
}
|
|
34
34
|
else {
|
|
35
35
|
throw new Error('Invalid arguments for getAwsCredentials');
|
|
@@ -55,11 +55,11 @@ export class StackAPI {
|
|
|
55
55
|
url = `https://${adminUrl}/.infra/credentials/k8s`;
|
|
56
56
|
}
|
|
57
57
|
else {
|
|
58
|
-
url = `${this.
|
|
58
|
+
url = `${this._stackApiBaseUrl}/v0/credentials/${gs.gameserver}/k8s`;
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
61
|
else if (gs.organization != null && gs.project != null && gs.environment != null) {
|
|
62
|
-
url = `${this.
|
|
62
|
+
url = `${this._stackApiBaseUrl}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}/credentials/k8s`;
|
|
63
63
|
}
|
|
64
64
|
else {
|
|
65
65
|
throw new Error('Invalid arguments for getKubeConfig');
|
|
@@ -77,6 +77,137 @@ export class StackAPI {
|
|
|
77
77
|
}
|
|
78
78
|
return await response.text();
|
|
79
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Get a `kubeconfig` payload which invokes `metaplay-auth get-kubernetes-execcredential` to get the actual
|
|
82
|
+
* access credentials each time the kubeconfig is used.
|
|
83
|
+
* @param gs Game server environment to get credentials for.
|
|
84
|
+
* @returns The kubeconfig YAML.
|
|
85
|
+
*/
|
|
86
|
+
async getKubeConfigExecCredential(gs) {
|
|
87
|
+
let url = '';
|
|
88
|
+
let gsSlug = '';
|
|
89
|
+
const execArgs = ['get-kubernetes-execcredential'];
|
|
90
|
+
if (gs.gameserver != null) {
|
|
91
|
+
const adminUrl = getGameserverAdminUrl(gs.gameserver);
|
|
92
|
+
url = `https://${adminUrl}/.infra/credentials/k8s?type=execcredential`;
|
|
93
|
+
gsSlug = gs.gameserver;
|
|
94
|
+
execArgs.push('--gameserver', gs.gameserver);
|
|
95
|
+
}
|
|
96
|
+
else if (gs.organization != null && gs.project != null && gs.environment != null) {
|
|
97
|
+
url = `${this._stackApiBaseUrl}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}/credentials/k8s?type=execcredential`;
|
|
98
|
+
gsSlug = `${gs.organization}.${gs.project}.${gs.environment}`;
|
|
99
|
+
execArgs.push('--organization', gs.organization, '--project', gs.project, '--environment', gs.environment);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
throw new Error('Invalid arguments for getKubeConfigExecCredential');
|
|
103
|
+
}
|
|
104
|
+
logger.debug(`Getting Kubernetes KubeConfig from ${url}...`);
|
|
105
|
+
const response = await fetch(url, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
headers: {
|
|
108
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
109
|
+
'Content-Type': 'application/json'
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
if (response.status !== 200) {
|
|
113
|
+
throw new Error(`Failed to fetch Kubernetes KubeConfig: ${response.statusText}`);
|
|
114
|
+
}
|
|
115
|
+
// get the execcredential and morph it into a kubeconfig which calls metaplay-auth for the token
|
|
116
|
+
let kubeExecCredential;
|
|
117
|
+
try {
|
|
118
|
+
kubeExecCredential = await response.json();
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
throw new Error('Failed to fetch Kubernetes KubeConfig: the server response is not JSON');
|
|
122
|
+
}
|
|
123
|
+
if (!kubeExecCredential.spec.cluster) {
|
|
124
|
+
throw new Error('Received kubeExecCredential with missing spec.cluster');
|
|
125
|
+
}
|
|
126
|
+
let namespace = 'default';
|
|
127
|
+
let user = 'user';
|
|
128
|
+
try {
|
|
129
|
+
const environment = await this.getEnvironmentDetails(gs);
|
|
130
|
+
namespace = environment.deployment?.kubernetes_namespace ?? namespace;
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
logger.debug('Failed to get environment details, using defaults', e);
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
const userinfo = await getUserinfo(this.accessToken);
|
|
137
|
+
user = userinfo.sub ?? user;
|
|
138
|
+
}
|
|
139
|
+
catch (e) {
|
|
140
|
+
logger.debug('Failed to get userinfo, using defaults', e);
|
|
141
|
+
}
|
|
142
|
+
const kubeConfig = {
|
|
143
|
+
apiVersion: 'v1',
|
|
144
|
+
kind: 'Config',
|
|
145
|
+
'current-context': gsSlug,
|
|
146
|
+
clusters: [
|
|
147
|
+
{
|
|
148
|
+
cluster: {
|
|
149
|
+
'certificate-authority-data': kubeExecCredential.spec.cluster.certificateAuthorityData,
|
|
150
|
+
server: kubeExecCredential.spec.cluster.server
|
|
151
|
+
},
|
|
152
|
+
name: kubeExecCredential.spec.cluster.server
|
|
153
|
+
}
|
|
154
|
+
],
|
|
155
|
+
contexts: [
|
|
156
|
+
{
|
|
157
|
+
context: {
|
|
158
|
+
cluster: kubeExecCredential.spec.cluster.server,
|
|
159
|
+
namespace,
|
|
160
|
+
user
|
|
161
|
+
},
|
|
162
|
+
name: gsSlug,
|
|
163
|
+
}
|
|
164
|
+
],
|
|
165
|
+
users: [
|
|
166
|
+
{
|
|
167
|
+
name: user,
|
|
168
|
+
user: {
|
|
169
|
+
exec: {
|
|
170
|
+
apiVersion: 'client.authentication.k8s.io/v1beta1',
|
|
171
|
+
command: 'metaplay-auth',
|
|
172
|
+
args: execArgs,
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
],
|
|
177
|
+
};
|
|
178
|
+
// return as yaml for easier consumption
|
|
179
|
+
return dump(kubeConfig);
|
|
180
|
+
}
|
|
181
|
+
async getKubeExecCredential(gs) {
|
|
182
|
+
let url = '';
|
|
183
|
+
if (gs.gameserver != null) {
|
|
184
|
+
if (isValidFQDN(gs.gameserver)) {
|
|
185
|
+
const adminUrl = getGameserverAdminUrl(gs.gameserver);
|
|
186
|
+
url = `https://${adminUrl}/.infra/credentials/k8s?type=execcredential`;
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
url = `${this._stackApiBaseUrl}/v0/credentials/${gs.gameserver}/k8s?type=execcredential`;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else if (gs.organization != null && gs.project != null && gs.environment != null) {
|
|
193
|
+
url = `${this._stackApiBaseUrl}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}/credentials/k8s?type=execcredential`;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
throw new Error('Invalid arguments for getKubeConfig');
|
|
197
|
+
}
|
|
198
|
+
logger.debug(`Getting Kubernetes ExecCredential from ${url}...`);
|
|
199
|
+
const response = await fetch(url, {
|
|
200
|
+
method: 'POST',
|
|
201
|
+
headers: {
|
|
202
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
203
|
+
'Content-Type': 'application/json'
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
if (response.status !== 200) {
|
|
207
|
+
throw new Error(`Failed to fetch Kubernetes ExecCredential: ${response.statusText}`);
|
|
208
|
+
}
|
|
209
|
+
return await response.text();
|
|
210
|
+
}
|
|
80
211
|
async getEnvironmentDetails(gs) {
|
|
81
212
|
let url = '';
|
|
82
213
|
if (gs.gameserver != null) {
|
|
@@ -85,11 +216,11 @@ export class StackAPI {
|
|
|
85
216
|
url = `https://${adminUrl}/.infra/environment`;
|
|
86
217
|
}
|
|
87
218
|
else {
|
|
88
|
-
url = `${this.
|
|
219
|
+
url = `${this._stackApiBaseUrl}/v0/deployments/${gs.gameserver}`;
|
|
89
220
|
}
|
|
90
221
|
}
|
|
91
222
|
else if (gs.organization != null && gs.project != null && gs.environment != null) {
|
|
92
|
-
url = `${this.
|
|
223
|
+
url = `${this._stackApiBaseUrl}/v1/servers/${gs.organization}/${gs.project}/${gs.environment}`;
|
|
93
224
|
}
|
|
94
225
|
else {
|
|
95
226
|
throw new Error('Invalid arguments for environment details');
|
package/dist/src/stackapi.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stackapi.js","sourceRoot":"","sources":["../../src/stackapi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"stackapi.js","sourceRoot":"","sources":["../../src/stackapi.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA;AAC9B,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAsEvC,MAAM,OAAO,QAAQ;IACF,WAAW,CAAQ;IAEnB,gBAAgB,CAAQ;IAEzC,YAAa,WAAmB,EAAE,eAAmC;QACnE,IAAI,WAAW,IAAI,IAAI,EAAE;YACvB,MAAM,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAA;SAChD;QAED,IAAI,CAAC,WAAW,GAAG,WAAW,CAAA;QAE9B,IAAI,eAAe,EAAE;YACnB,IAAI,CAAC,gBAAgB,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA,CAAC,wBAAwB;SACpF;aAAM;YACL,IAAI,CAAC,gBAAgB,GAAG,uCAAuC,CAAA;SAChE;IACH,CAAC;IAED,KAAK,CAAC,iBAAiB,CAAE,EAAgB;QACvC,IAAI,GAAG,GAAG,EAAE,CAAA;QACZ,IAAI,EAAE,CAAC,UAAU,IAAI,IAAI,EAAE;YACzB,IAAI,WAAW,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE;gBAC9B,MAAM,QAAQ,GAAG,qBAAqB,CAAC,EAAE,CAAC,UAAU,CAAC,CAAA;gBACrD,GAAG,GAAG,WAAW,QAAQ,yBAAyB,CAAA;aACnD;iBAAM;gBACL,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,mBAAmB,EAAE,CAAC,UAAU,MAAM,CAAA;aACrE;SACF;aAAM,IAAI,EAAE,CAAC,YAAY,IAAI,IAAI,IAAI,EAAE,CAAC,OAAO,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,IAAI,IAAI,EAAE;YAClF,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,eAAe,EAAE,CAAC,YAAY,IAAI,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,WAAW,kBAAkB,CAAA;SAC/G;aAAM;YACL,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAA;SAC3D;QAED,MAAM,CAAC,KAAK,CAAC,gCAAgC,GAAG,KAAK,CAAC,CAAA;QACtD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,IAAI,CAAC,WAAW,EAAE;gBAC3C,cAAc,EAAE,kBAAkB;aACnC;SACF,CAAC,CAAA;QAEF,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE;YAC3B,MAAM,IAAI,KAAK,CAAC,oCAAoC,QAAQ,CAAC,UAAU,mBAAmB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;SAC7G;QAED,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;IAC9B,CAAC;IAED,KAAK,CAAC,aAAa,CAAE,EAAgB;QACnC,IAAI,GAAG,GAAG,EAAE,CAAA;QACZ,IAAI,EAAE,CAAC,UAAU,IAAI,IAAI,EAAE;YACzB,IAAI,WAAW,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE;gBAC9B,MAAM,QAAQ,GAAG,qBAAqB,CAAC,EAAE,CAAC,UAAU,CAAC,CAAA;gBACrD,GAAG,GAAG,WAAW,QAAQ,yBAAyB,CAAA;aACnD;iBAAM;gBACL,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,mBAAmB,EAAE,CAAC,UAAU,MAAM,CAAA;aACrE;SACF;aAAM,IAAI,EAAE,CAAC,YAAY,IAAI,IAAI,IAAI,EAAE,CAAC,OAAO,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,IAAI,IAAI,EAAE;YAClF,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,eAAe,EAAE,CAAC,YAAY,IAAI,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,WAAW,kBAAkB,CAAA;SAC/G;aAAM;YACL,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;SACvD;QAED,MAAM,CAAC,KAAK,CAAC,2BAA2B,GAAG,KAAK,CAAC,CAAA;QAEjD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,IAAI,CAAC,WAAW,EAAE;gBAC3C,cAAc,EAAE,kBAAkB;aACnC;SACF,CAAC,CAAA;QAEF,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE;YAC3B,MAAM,IAAI,KAAK,CAAC,gCAAgC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAA;SACvE;QAED,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;IAC9B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,2BAA2B,CAAE,EAAgB;QACjD,IAAI,GAAG,GAAG,EAAE,CAAA;QACZ,IAAI,MAAM,GAAG,EAAE,CAAA;QACf,MAAM,QAAQ,GAAG,CAAC,+BAA+B,CAAC,CAAA;QAClD,IAAI,EAAE,CAAC,UAAU,IAAI,IAAI,EAAE;YACzB,MAAM,QAAQ,GAAG,qBAAqB,CAAC,EAAE,CAAC,UAAU,CAAC,CAAA;YACrD,GAAG,GAAG,WAAW,QAAQ,6CAA6C,CAAA;YACtE,MAAM,GAAG,EAAE,CAAC,UAAU,CAAA;YACtB,QAAQ,CAAC,IAAI,CAAC,cAAc,EAAE,EAAE,CAAC,UAAU,CAAC,CAAA;SAC7C;aAAM,IAAI,EAAE,CAAC,YAAY,IAAI,IAAI,IAAI,EAAE,CAAC,OAAO,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,IAAI,IAAI,EAAE;YAClF,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,eAAe,EAAE,CAAC,YAAY,IAAI,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,WAAW,sCAAsC,CAAA;YAClI,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,IAAI,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;YAC7D,QAAQ,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC,YAAY,EAAE,WAAW,EAAE,EAAE,CAAC,OAAO,EAAE,eAAe,EAAE,EAAE,CAAC,WAAW,CAAC,CAAA;SAC3G;aAAM;YACL,MAAM,IAAI,KAAK,CAAC,mDAAmD,CAAC,CAAA;SACrE;QAED,MAAM,CAAC,KAAK,CAAC,sCAAsC,GAAG,KAAK,CAAC,CAAA;QAE5D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,IAAI,CAAC,WAAW,EAAE;gBAC3C,cAAc,EAAE,kBAAkB;aACnC;SACF,CAAC,CAAA;QAEF,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE;YAC3B,MAAM,IAAI,KAAK,CAAC,0CAA0C,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAA;SACjF;QAED,gGAAgG;QAChG,IAAI,kBAAkB,CAAA;QACtB,IAAI;YACF,kBAAkB,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAwB,CAAA;SACjE;QAAC,MAAM;YACN,MAAM,IAAI,KAAK,CAAC,wEAAwE,CAAC,CAAA;SAC1F;QAED,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,OAAO,EAAE;YACpC,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAA;SACzE;QAED,IAAI,SAAS,GAAG,SAAS,CAAA;QACzB,IAAI,IAAI,GAAG,MAAM,CAAA;QAEjB,IAAI;YACF,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAA;YACxD,SAAS,GAAG,WAAW,CAAC,UAAU,EAAE,oBAAoB,IAAI,SAAS,CAAA;SACtE;QAAC,OAAO,CAAC,EAAE;YACV,MAAM,CAAC,KAAK,CAAC,mDAAmD,EAAE,CAAC,CAAC,CAAA;SACrE;QAED,IAAI;YACF,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YACpD,IAAI,GAAG,QAAQ,CAAC,GAAG,IAAI,IAAI,CAAA;SAC5B;QAAC,OAAO,CAAC,EAAE;YACV,MAAM,CAAC,KAAK,CAAC,wCAAwC,EAAE,CAAC,CAAC,CAAA;SAC1D;QAED,MAAM,UAAU,GAAe;YAC7B,UAAU,EAAE,IAAI;YAChB,IAAI,EAAE,QAAQ;YACd,iBAAiB,EAAE,MAAM;YACzB,QAAQ,EAAE;gBACR;oBACE,OAAO,EAAE;wBACP,4BAA4B,EAAE,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,wBAAwB;wBACtF,MAAM,EAAE,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM;qBAC/C;oBACD,IAAI,EAAE,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM;iBAC7C;aACF;YACD,QAAQ,EAAE;gBACR;oBACE,OAAO,EAAE;wBACP,OAAO,EAAE,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM;wBAC/C,SAAS;wBACT,IAAI;qBACL;oBACD,IAAI,EAAE,MAAM;iBACb;aACF;YACD,KAAK,EAAE;gBACL;oBACE,IAAI,EAAE,IAAI;oBACV,IAAI,EAAE;wBACJ,IAAI,EAAE;4BACJ,UAAU,EAAE,sCAAsC;4BAClD,OAAO,EAAE,eAAe;4BACxB,IAAI,EAAE,QAAQ;yBACf;qBACF;iBACF;aACF;SACF,CAAA;QAED,wCAAwC;QACxC,OAAO,IAAI,CAAC,UAAU,CAAC,CAAA;IACzB,CAAC;IAED,KAAK,CAAC,qBAAqB,CAAE,EAAgB;QAC3C,IAAI,GAAG,GAAG,EAAE,CAAA;QACZ,IAAI,EAAE,CAAC,UAAU,IAAI,IAAI,EAAE;YACzB,IAAI,WAAW,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE;gBAC9B,MAAM,QAAQ,GAAG,qBAAqB,CAAC,EAAE,CAAC,UAAU,CAAC,CAAA;gBACrD,GAAG,GAAG,WAAW,QAAQ,6CAA6C,CAAA;aACvE;iBAAM;gBACL,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,mBAAmB,EAAE,CAAC,UAAU,0BAA0B,CAAA;aACzF;SACF;aAAM,IAAI,EAAE,CAAC,YAAY,IAAI,IAAI,IAAI,EAAE,CAAC,OAAO,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,IAAI,IAAI,EAAE;YAClF,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,eAAe,EAAE,CAAC,YAAY,IAAI,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,WAAW,sCAAsC,CAAA;SACnI;aAAM;YACL,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;SACvD;QAED,MAAM,CAAC,KAAK,CAAC,0CAA0C,GAAG,KAAK,CAAC,CAAA;QAEhE,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,IAAI,CAAC,WAAW,EAAE;gBAC3C,cAAc,EAAE,kBAAkB;aACnC;SACF,CAAC,CAAA;QAEF,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE;YAC3B,MAAM,IAAI,KAAK,CAAC,8CAA8C,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAA;SACrF;QAED,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;IAC9B,CAAC;IAED,KAAK,CAAC,qBAAqB,CAAE,EAAgB;QAC3C,IAAI,GAAG,GAAG,EAAE,CAAA;QACZ,IAAI,EAAE,CAAC,UAAU,IAAI,IAAI,EAAE;YACzB,IAAI,WAAW,CAAC,EAAE,CAAC,UAAU,CAAC,EAAE;gBAC9B,MAAM,QAAQ,GAAG,qBAAqB,CAAC,EAAE,CAAC,UAAU,CAAC,CAAA;gBACrD,GAAG,GAAG,WAAW,QAAQ,qBAAqB,CAAA;aAC/C;iBAAM;gBACL,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,mBAAmB,EAAE,CAAC,UAAU,EAAE,CAAA;aACjE;SACF;aAAM,IAAI,EAAE,CAAC,YAAY,IAAI,IAAI,IAAI,EAAE,CAAC,OAAO,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,IAAI,IAAI,EAAE;YAClF,GAAG,GAAG,GAAG,IAAI,CAAC,gBAAgB,eAAe,EAAE,CAAC,YAAY,IAAI,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;SAC/F;aAAM;YACL,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAA;SAC7D;QAED,MAAM,CAAC,KAAK,CAAC,oCAAoC,GAAG,KAAK,CAAC,CAAA;QAC1D,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,KAAK;YACb,OAAO,EAAE;gBACP,aAAa,EAAE,UAAU,IAAI,CAAC,WAAW,EAAE;gBAC3C,cAAc,EAAE,kBAAkB;aACnC;SACF,CAAC,CAAA;QAEF,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE;YAC3B,MAAM,IAAI,KAAK,CAAC,wCAAwC,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAA;SAC/E;QAED,OAAO,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAA;IAC9B,CAAC;CACF"}
|
package/index.ts
CHANGED
|
@@ -2,17 +2,42 @@
|
|
|
2
2
|
import { Command } from 'commander'
|
|
3
3
|
import { loginAndSaveTokens, machineLoginAndSaveTokens, extendCurrentSession, loadTokens, removeTokens } from './src/auth.js'
|
|
4
4
|
import { StackAPI } from './src/stackapi.js'
|
|
5
|
+
import type { GameserverId } from './src/stackapi.js'
|
|
5
6
|
import { checkGameServerDeployment } from './src/deployment.js'
|
|
6
7
|
import { logger, setLogLevel } from './src/logging.js'
|
|
8
|
+
import { isValidFQDN } from './src/utils.js'
|
|
7
9
|
import { exit } from 'process'
|
|
8
10
|
import { ECRClient, GetAuthorizationTokenCommand } from '@aws-sdk/client-ecr'
|
|
9
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Helper for parsing the GameserverId type from the command line arguments. Accepts either the gameserver address or the
|
|
14
|
+
* (organization, project, environment) tuple from options.
|
|
15
|
+
*/
|
|
16
|
+
function resolveGameserverId (address: string | undefined, options: any): GameserverId {
|
|
17
|
+
// If address is specified, use it, otherwise assume options has organization, project, and environment
|
|
18
|
+
if (address) {
|
|
19
|
+
if (isValidFQDN(address)) {
|
|
20
|
+
return { gameserver: address }
|
|
21
|
+
} else {
|
|
22
|
+
const parts = address.split('-')
|
|
23
|
+
if (parts.length !== 3) {
|
|
24
|
+
throw new Error('Invalid gameserver address syntax: specify either <organiation>-<project>-<environment> or a fully-qualified domain name (eg, idler-develop.p1.metaplay.io)')
|
|
25
|
+
}
|
|
26
|
+
return { organization: parts[0], project: parts[1], environment: parts[2] }
|
|
27
|
+
}
|
|
28
|
+
} else if (options.organization && options.project && options.environment) {
|
|
29
|
+
return { organization: options.organization, project: options.project, environment: options.environment }
|
|
30
|
+
} else {
|
|
31
|
+
throw new Error('Could not determine target environment from arguments: You need to specify either a gameserver address or an organization, project, and environment. Run this command with --help flag for more information.')
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
10
35
|
const program = new Command()
|
|
11
36
|
|
|
12
37
|
program
|
|
13
38
|
.name('metaplay-auth')
|
|
14
39
|
.description('Authenticate with Metaplay and get AWS and Kubernetes credentials for game servers.')
|
|
15
|
-
.version('1.
|
|
40
|
+
.version('1.3.0')
|
|
16
41
|
.option('-d, --debug', 'enable debug output')
|
|
17
42
|
.hook('preAction', (thisCommand) => {
|
|
18
43
|
// Handle debug flag for all commands.
|
|
@@ -94,43 +119,76 @@ program.command('show-tokens')
|
|
|
94
119
|
})
|
|
95
120
|
|
|
96
121
|
program.command('get-kubeconfig')
|
|
97
|
-
.description('get kubeconfig for
|
|
98
|
-
.argument('[gameserver]', 'address of gameserver (e.g. idler-develop.p1.metaplay.io)')
|
|
122
|
+
.description('get kubeconfig for target environment')
|
|
123
|
+
.argument('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
|
|
99
124
|
.option('-s, --stack-api <stack-api-base-path>', 'explicit stack api (e.g. https://infra.p1.metaplay.io/stackapi/)')
|
|
100
125
|
.option('-o, --organization <organization>', 'organization name (e.g. metaplay)')
|
|
101
126
|
.option('-p, --project <project>', 'project name (e.g. idler)')
|
|
102
127
|
.option('-e, --environment <environment>', 'environment name (e.g. develop)')
|
|
128
|
+
.option('-t, --type <format>', 'output format (static or dynamic)', 'static')
|
|
103
129
|
.hook('preAction', async () => {
|
|
104
130
|
await extendCurrentSession()
|
|
105
131
|
})
|
|
106
132
|
.action(async (gameserver, options) => {
|
|
107
133
|
try {
|
|
108
134
|
const tokens = await loadTokens()
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
135
|
+
const stackApi = new StackAPI(tokens.access_token, options.stackApi)
|
|
136
|
+
const gameserverId = resolveGameserverId(gameserver, options)
|
|
137
|
+
|
|
138
|
+
if (options.type === 'dynamic') {
|
|
139
|
+
const credentials = await stackApi.getKubeConfigExecCredential(gameserverId)
|
|
140
|
+
console.log(credentials)
|
|
141
|
+
} else if (options.type === 'static') {
|
|
142
|
+
const credentials = await stackApi.getKubeConfig(gameserverId)
|
|
143
|
+
console.log(credentials)
|
|
144
|
+
} else {
|
|
145
|
+
throw new Error('Invalid type; must be one of static or dynamic')
|
|
112
146
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
stackApi.stack_api_base_uri = options.stackApi
|
|
147
|
+
} catch (error) {
|
|
148
|
+
if (error instanceof Error) {
|
|
149
|
+
console.error('Error getting KubeConfig:', error)
|
|
117
150
|
}
|
|
151
|
+
exit(1)
|
|
152
|
+
}
|
|
153
|
+
})
|
|
118
154
|
|
|
119
|
-
|
|
155
|
+
/**
|
|
156
|
+
* Get the Kubernetes credentials in the execcredential format which can be used within the `kubeconfig` file:
|
|
157
|
+
* The kubeconfig can invoke this command to fetch the Kubernetes credentials just-in-time which allows us to
|
|
158
|
+
* generate kubeconfig files that don't contain access tokens and are longer-lived (the authentication is the
|
|
159
|
+
* same as that of metaplay-auth itself). Use `metaplay-auth get-kubeconfig -t dynamic ...` to create a
|
|
160
|
+
* kubeconfig that uses this command.
|
|
161
|
+
*/
|
|
162
|
+
// todo: maybe this should be a hidden command as it's not very useful for end users and clutters help?
|
|
163
|
+
program.command('get-kubernetes-execcredential')
|
|
164
|
+
.description('get kubernetes credentials in execcredential format (only intended to be used within a kubeconfig)')
|
|
165
|
+
.argument('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
|
|
166
|
+
.option('-s, --stack-api <stack-api-base-path>', 'explicit stack api (e.g. https://infra.p1.metaplay.io/stackapi/)')
|
|
167
|
+
.option('-o, --organization <organization>', 'organization name (e.g. metaplay)')
|
|
168
|
+
.option('-p, --project <project>', 'project name (e.g. idler)')
|
|
169
|
+
.option('-e, --environment <environment>', 'environment name (e.g. develop)')
|
|
170
|
+
.hook('preAction', async () => {
|
|
171
|
+
await extendCurrentSession()
|
|
172
|
+
})
|
|
173
|
+
.action(async (gameserver, options) => {
|
|
174
|
+
try {
|
|
175
|
+
const tokens = await loadTokens()
|
|
176
|
+
const stackApi = new StackAPI(tokens.access_token, options.stackApi)
|
|
177
|
+
const gameserverId = resolveGameserverId(gameserver, options)
|
|
120
178
|
|
|
121
|
-
const credentials = await stackApi.
|
|
179
|
+
const credentials = await stackApi.getKubeExecCredential(gameserverId)
|
|
122
180
|
console.log(credentials)
|
|
123
181
|
} catch (error) {
|
|
124
182
|
if (error instanceof Error) {
|
|
125
|
-
console.error(`Error getting
|
|
183
|
+
console.error(`Error getting Kubernetes ExecCredential: ${error.message}`)
|
|
126
184
|
}
|
|
127
185
|
exit(1)
|
|
128
186
|
}
|
|
129
187
|
})
|
|
130
188
|
|
|
131
189
|
program.command('get-aws-credentials')
|
|
132
|
-
.description('get AWS credentials for
|
|
133
|
-
.argument('[gameserver]', 'address of gameserver (e.g. idler-develop.p1.metaplay.io)')
|
|
190
|
+
.description('get AWS credentials for target environment')
|
|
191
|
+
.argument('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
|
|
134
192
|
.option('-s, --stack-api <stack-api-base-path>', 'explicit stack api (e.g. https://infra.p1.metaplay.io/stackapi/)')
|
|
135
193
|
.option('-o, --organization <organization>', 'organization name (e.g. metaplay)')
|
|
136
194
|
.option('-p, --project <project>', 'project name (e.g. idler)')
|
|
@@ -146,19 +204,10 @@ program.command('get-aws-credentials')
|
|
|
146
204
|
}
|
|
147
205
|
|
|
148
206
|
const tokens = await loadTokens()
|
|
207
|
+
const stackApi = new StackAPI(tokens.access_token, options.stackApi)
|
|
208
|
+
const gameserverId = resolveGameserverId(gameserver, options)
|
|
149
209
|
|
|
150
|
-
|
|
151
|
-
throw new Error('Could not determine a deployment to fetch the AWS credentials from. You need to specify either a gameserver or an organization, project, and environment')
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
const stackApi = new StackAPI(tokens.access_token)
|
|
155
|
-
if (options.stackApi) {
|
|
156
|
-
stackApi.stack_api_base_uri = options.stackApi
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const payload = gameserver ? { gameserver } : { organization: options.organization, project: options.project, environment: options.environment }
|
|
160
|
-
|
|
161
|
-
const credentials = await stackApi.getAwsCredentials(payload)
|
|
210
|
+
const credentials = await stackApi.getAwsCredentials(gameserverId)
|
|
162
211
|
|
|
163
212
|
if (options.format === 'env') {
|
|
164
213
|
console.log(`export AWS_ACCESS_KEY_ID=${credentials.AccessKeyId}`)
|
|
@@ -179,8 +228,8 @@ program.command('get-aws-credentials')
|
|
|
179
228
|
})
|
|
180
229
|
|
|
181
230
|
program.command('get-docker-login')
|
|
182
|
-
.description('get docker login credentials for pushing the server image')
|
|
183
|
-
.argument('[gameserver]', 'address of gameserver (e.g. idler-develop.p1.metaplay.io)')
|
|
231
|
+
.description('get docker login credentials for pushing the server image to target environment')
|
|
232
|
+
.argument('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
|
|
184
233
|
.option('-o, --organization <organization>', 'organization name (e.g. metaplay)')
|
|
185
234
|
.option('-p, --project <project>', 'project name (e.g. idler)')
|
|
186
235
|
.option('-e, --environment <environment>', 'environment name (e.g. develop)')
|
|
@@ -195,24 +244,16 @@ program.command('get-docker-login')
|
|
|
195
244
|
}
|
|
196
245
|
|
|
197
246
|
const tokens = await loadTokens()
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
throw new Error('Could not determine a game server deployment. You need to specify either a gameserver or an organization, project, and environment')
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const stackApi = new StackAPI(tokens.access_token)
|
|
204
|
-
if (options.stackApi) {
|
|
205
|
-
stackApi.stack_api_base_uri = options.stackApi
|
|
206
|
-
}
|
|
247
|
+
const stackApi = new StackAPI(tokens.access_token, options.stackApi)
|
|
248
|
+
const gameserverId = resolveGameserverId(gameserver, options)
|
|
207
249
|
|
|
208
250
|
// Fetch AWS credentials from Metaplay cloud
|
|
209
251
|
logger.debug('Get AWS credentials from Metaplay')
|
|
210
|
-
const
|
|
211
|
-
const credentials = await stackApi.getAwsCredentials(payload)
|
|
252
|
+
const credentials = await stackApi.getAwsCredentials(gameserverId)
|
|
212
253
|
|
|
213
254
|
// Get environment info (region is needed for ECR)
|
|
214
255
|
logger.debug('Get environment info')
|
|
215
|
-
const environment = await stackApi.getEnvironmentDetails(
|
|
256
|
+
const environment = await stackApi.getEnvironmentDetails(gameserverId)
|
|
216
257
|
const awsRegion = environment.deployment.aws_region
|
|
217
258
|
const dockerRepo = environment.deployment.ecr_repo
|
|
218
259
|
|
|
@@ -262,8 +303,8 @@ program.command('get-docker-login')
|
|
|
262
303
|
})
|
|
263
304
|
|
|
264
305
|
program.command('get-environment')
|
|
265
|
-
.description('get
|
|
266
|
-
.argument('[gameserver]', 'address of gameserver (e.g. idler-develop.p1.metaplay.io)')
|
|
306
|
+
.description('get details of an environment')
|
|
307
|
+
.argument('[gameserver]', 'address of gameserver (e.g. metaplay-idler-develop or idler-develop.p1.metaplay.io)')
|
|
267
308
|
.option('-s, --stack-api <stack-api-base-path>', 'explicit stack api (e.g. https://infra.p1.metaplay.io/stackapi/)')
|
|
268
309
|
.option('-o, --organization <organization>', 'organization name (e.g. metaplay)')
|
|
269
310
|
.option('-p, --project <project>', 'project name (e.g. idler)')
|
|
@@ -274,19 +315,10 @@ program.command('get-environment')
|
|
|
274
315
|
.action(async (gameserver, options) => {
|
|
275
316
|
try {
|
|
276
317
|
const tokens = await loadTokens()
|
|
318
|
+
const stackApi = new StackAPI(tokens.access_token, options.stackApi)
|
|
319
|
+
const gameserverId = resolveGameserverId(gameserver, options)
|
|
277
320
|
|
|
278
|
-
|
|
279
|
-
throw new Error('Could not determine a deployment to fetch environment details from. You need to specify either a gameserver or an organization, project, and environment')
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const stackApi = new StackAPI(tokens.access_token)
|
|
283
|
-
if (options.stackApi) {
|
|
284
|
-
stackApi.stack_api_base_uri = options.stackApi
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const payload = gameserver ? { gameserver } : { organization: options.organization, project: options.project, environment: options.environment }
|
|
288
|
-
|
|
289
|
-
const environment = await stackApi.getEnvironmentDetails(payload)
|
|
321
|
+
const environment = await stackApi.getEnvironmentDetails(gameserverId)
|
|
290
322
|
console.log(JSON.stringify(environment))
|
|
291
323
|
} catch (error) {
|
|
292
324
|
if (error instanceof Error) {
|
|
@@ -310,8 +342,8 @@ program.command('check-deployment')
|
|
|
310
342
|
// Run the checks and exit with success/failure exitCode depending on result
|
|
311
343
|
const exitCode = await checkGameServerDeployment(namespace)
|
|
312
344
|
exit(exitCode)
|
|
313
|
-
} catch (error) {
|
|
314
|
-
console.error(`Failed to check deployment status: ${error}`)
|
|
345
|
+
} catch (error: any) {
|
|
346
|
+
console.error(`Failed to check deployment status: ${error.message}`)
|
|
315
347
|
exit(1)
|
|
316
348
|
}
|
|
317
349
|
})
|
package/package.json
CHANGED
|
@@ -1,38 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metaplay/metaplay-auth",
|
|
3
3
|
"description": "Utility CLI for authenticating with the Metaplay Auth and making authenticated calls to infrastructure endpoints.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|
|
7
7
|
"homepage": "https://metaplay.io",
|
|
8
8
|
"bin": "dist/index.js",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "tsx index.ts",
|
|
11
|
+
"prepublish": "tsc"
|
|
12
|
+
},
|
|
9
13
|
"publishConfig": {
|
|
10
14
|
"access": "public"
|
|
11
15
|
},
|
|
12
16
|
"devDependencies": {
|
|
17
|
+
"@metaplay/eslint-config": "workspace:*",
|
|
18
|
+
"@metaplay/typescript-config": "workspace:*",
|
|
13
19
|
"@types/express": "^4.17.21",
|
|
20
|
+
"@types/js-yaml": "^4.0.9",
|
|
14
21
|
"@types/jsonwebtoken": "^9.0.5",
|
|
15
22
|
"@types/jwk-to-pem": "^2.0.3",
|
|
16
|
-
"@types/node": "^20.
|
|
23
|
+
"@types/node": "^20.12.5",
|
|
17
24
|
"tsx": "^4.7.1",
|
|
18
25
|
"typescript": "^5.1.6",
|
|
19
|
-
"vitest": "^1.
|
|
20
|
-
"@metaplay/eslint-config": "1.0.0",
|
|
21
|
-
"@metaplay/typescript-config": "1.0.0"
|
|
26
|
+
"vitest": "^1.4.0"
|
|
22
27
|
},
|
|
23
28
|
"dependencies": {
|
|
24
|
-
"@aws-sdk/client-ecr": "^3.
|
|
25
|
-
"@kubernetes/client-node": "^0.
|
|
26
|
-
"@ory/client": "^1.
|
|
29
|
+
"@aws-sdk/client-ecr": "^3.549.0",
|
|
30
|
+
"@kubernetes/client-node": "^1.0.0-rc4",
|
|
31
|
+
"@ory/client": "^1.9.0",
|
|
27
32
|
"commander": "^12.0.0",
|
|
28
|
-
"h3": "^1.
|
|
33
|
+
"h3": "^1.11.1",
|
|
34
|
+
"js-yaml": "^4.1.0",
|
|
29
35
|
"jsonwebtoken": "^9.0.2",
|
|
30
36
|
"jwk-to-pem": "^2.0.5",
|
|
31
|
-
"open": "^10.0
|
|
37
|
+
"open": "^10.1.0",
|
|
32
38
|
"tslog": "^4.9.2"
|
|
33
|
-
},
|
|
34
|
-
"scripts": {
|
|
35
|
-
"dev": "tsx index.ts",
|
|
36
|
-
"prepublish": "tsc"
|
|
37
39
|
}
|
|
38
40
|
}
|
package/src/auth.ts
CHANGED
|
@@ -7,7 +7,7 @@ import open from 'open'
|
|
|
7
7
|
import { randomBytes, createHash } from 'node:crypto'
|
|
8
8
|
import jwt from 'jsonwebtoken'
|
|
9
9
|
import jwkToPem from 'jwk-to-pem'
|
|
10
|
-
import { Configuration, WellknownApi } from '@ory/client'
|
|
10
|
+
import { Configuration, WellknownApi, OidcApi } from '@ory/client'
|
|
11
11
|
import { setSecret, getSecret, removeSecret } from './secret_store.js'
|
|
12
12
|
|
|
13
13
|
import { logger } from './logging.js'
|
|
@@ -20,6 +20,9 @@ const tokenEndpoint = `${baseURL}/oauth2/token`
|
|
|
20
20
|
const wellknownApi = new WellknownApi(new Configuration({
|
|
21
21
|
basePath: baseURL,
|
|
22
22
|
}))
|
|
23
|
+
const oidcApi = new OidcApi(new Configuration({
|
|
24
|
+
basePath: baseURL,
|
|
25
|
+
}))
|
|
23
26
|
|
|
24
27
|
/**
|
|
25
28
|
* A helper function which generates a code verifier and challenge for exchaning code from Ory server.
|
|
@@ -31,6 +34,34 @@ function generateCodeVerifierAndChallenge (): { verifier: string, challenge: str
|
|
|
31
34
|
return { verifier, challenge }
|
|
32
35
|
}
|
|
33
36
|
|
|
37
|
+
/**
|
|
38
|
+
* A helper function which fetches the user's info from an OIDC userinfo endpoint for the given token.
|
|
39
|
+
* @param token The token to fetch the userinfo for.
|
|
40
|
+
* @returns An object containing the user's info.
|
|
41
|
+
*/
|
|
42
|
+
export async function getUserinfo (token: string): Promise<any> {
|
|
43
|
+
logger.debug('Trying to find OIDC well-known endpoints...')
|
|
44
|
+
const oidcRes = await oidcApi.discoverOidcConfiguration()
|
|
45
|
+
|
|
46
|
+
const userinfoEndpoint = oidcRes.data?.userinfo_endpoint
|
|
47
|
+
if (!userinfoEndpoint) {
|
|
48
|
+
throw new Error('No userinfo endpoint found in OIDC configuration')
|
|
49
|
+
}
|
|
50
|
+
logger.debug(`Found userinfo endpoint: ${userinfoEndpoint}`)
|
|
51
|
+
|
|
52
|
+
const userinfoRes = await fetch(userinfoEndpoint, {
|
|
53
|
+
headers: {
|
|
54
|
+
Authorization: `Bearer ${token}`
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
if (userinfoRes.status < 200 || userinfoRes.status >= 300) {
|
|
59
|
+
throw new Error(`Failed to fetch userinfo: ${userinfoRes.status} ${userinfoRes.statusText}`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return await userinfoRes.json()
|
|
63
|
+
}
|
|
64
|
+
|
|
34
65
|
/**
|
|
35
66
|
* A helper function which finds an local available port to listen on.
|
|
36
67
|
* @returns A promise that resolves to an available port.
|
package/src/deployment.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from 'fs'
|
|
2
2
|
import { KubeConfig, CoreV1Api, V1Pod } from '@kubernetes/client-node'
|
|
3
3
|
import { logger } from './logging.js'
|
|
4
|
-
import {
|
|
4
|
+
import type { CoreV1ApiListNamespacedPodRequest, CoreV1ApiReadNamespacedPodLogRequest } from '@kubernetes/client-node'
|
|
5
5
|
|
|
6
6
|
enum GameServerPodPhase {
|
|
7
7
|
Pending = 'Pending', // Still being deployed
|
|
@@ -18,14 +18,17 @@ interface GameServerPodStatus {
|
|
|
18
18
|
|
|
19
19
|
async function fetchGameServerPods (k8sApi: CoreV1Api, namespace: string) {
|
|
20
20
|
// Define label selector for gameserver
|
|
21
|
-
const
|
|
21
|
+
const param: CoreV1ApiListNamespacedPodRequest = {
|
|
22
|
+
namespace,
|
|
23
|
+
labelSelector: 'app=metaplay-server'
|
|
24
|
+
}
|
|
22
25
|
|
|
23
26
|
try {
|
|
24
27
|
// Get gameserver pods in the namespace
|
|
25
|
-
const response = await k8sApi.listNamespacedPod(
|
|
28
|
+
const response = await k8sApi.listNamespacedPod(param)
|
|
26
29
|
|
|
27
30
|
// Return pod statuses
|
|
28
|
-
return response.
|
|
31
|
+
return response.items
|
|
29
32
|
} catch (error) {
|
|
30
33
|
// \todo Better error handling ..
|
|
31
34
|
console.log('Failed to fetch pods from Kubernetes:', error)
|
|
@@ -168,13 +171,19 @@ async function fetchPodLogs (k8sApi: CoreV1Api, pod: V1Pod) {
|
|
|
168
171
|
throw new Error('Unable to determine pod metadata')
|
|
169
172
|
}
|
|
170
173
|
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
174
|
+
const params: CoreV1ApiReadNamespacedPodLogRequest = {
|
|
175
|
+
name: podName,
|
|
176
|
+
namespace,
|
|
177
|
+
container: containerName,
|
|
178
|
+
pretty: 'true',
|
|
179
|
+
previous: false,
|
|
180
|
+
tailLines: 100,
|
|
181
|
+
timestamps: true
|
|
182
|
+
}
|
|
183
|
+
|
|
175
184
|
try {
|
|
176
|
-
const response = await k8sApi.readNamespacedPodLog(
|
|
177
|
-
return response
|
|
185
|
+
const response: string = await k8sApi.readNamespacedPodLog(params)
|
|
186
|
+
return response
|
|
178
187
|
} catch (error) {
|
|
179
188
|
// \todo Better error handling ..
|
|
180
189
|
console.log('Failed to fetch pod logs from Kubernetes:', error)
|
|
@@ -218,7 +227,12 @@ export async function checkGameServerDeployment (namespace: string): Promise<num
|
|
|
218
227
|
|
|
219
228
|
// Create Kubernetes API instance (with default kubeconfig)
|
|
220
229
|
const kc = new KubeConfig()
|
|
221
|
-
|
|
230
|
+
// Loda kubeconfig from file and throw error if validation fails.
|
|
231
|
+
try {
|
|
232
|
+
kc.loadFromFile(kubeconfigPath)
|
|
233
|
+
} catch (error) {
|
|
234
|
+
throw new Error(`Failed to load or validate kubeconfig. ${error}`)
|
|
235
|
+
}
|
|
222
236
|
const k8sApi = kc.makeApiClient(CoreV1Api)
|
|
223
237
|
|
|
224
238
|
// Figure out when to stop
|