#node.js #amazon-web-services #aws-sdk #saml #adfs2.0
#node.js #amazon-веб-сервисы #aws-sdk #saml #adfs2.0
Вопрос:
Я ищу предложения о том, как аутентифицировать сервер NodeJS в моей учетной записи AWS, используя ADFS с именем пользователя и паролем, за которым следует аутентификация MFA, чтобы получить временный ключ доступа и секретный ключ для дальнейших действий.
Для справки, я пытаюсь добиться того, что saml2aws делает в интерфейсе командной строки. Здесь вместо интерфейса командной строки я хотел бы сделать это на моем сервере NodeJS.
Любые предложения будут высоко оценены.
Пока у меня есть приведенный ниже код, он выполняет за меня половину работы, например, вводит учетные данные ADFS и отправляет запрос, а затем запускает уведомление для подтверждения в моем приложении Microsoft authenticator. Но мой код не ожидает ответа этого аутентификатора. Он просто получает тело веб-страницы, где он ожидает подтверждения аутентификатора. Мне нужен какой-то способ заставить его ждать ответа аутентификатора, который даст мне ответ утверждения SAML после отправки кредита ADFS.
require("dotenv").config();
const AWS = require("aws-sdk");
const sts = new AWS.STS();
const request = require("request").defaults({ jar: true });
const url = require("url");
const JSSoup = require("jssoup").default;
const os = require("os");
const path = require("path");
const fs = require("fs");
const ini = require("ini");
const HOME = os.homedir();
const CONFIG_FILE = path.join(HOME, ".aws", "credentials");
let IDP_URL = process.env.IDP_URL;
let IDP_USER = process.env.IDP_USER;
let IDP_PASS = process.env.IDP_PASS;
let AWS_PROFILE = process.env.AWS_PROFILE;
console.log(IDP_USER);
function base64encode(data) {
return Buffer.from(data, "utf8").toString("base64");
}
function base64decode(data) {
return Buffer.from(data, "base64").toString("utf8");
}
function httpGet(url) {
const options = {
url,
};
return new Promise((resolve, reject) => {
request.get(options, (error, response, body) => {
if (error) {
reject(error);
} else {
resolve({ response, body });
}
});
});
}
function httpPost(url, form) {
const options = {
url,
form,
followAllRedirects: true,
};
return new Promise((resolve, reject) => {
request.post(options, (error, response, body) => {
if (error) {
reject(error);
} else {
resolve({ response, body });
}
});
});
}
function getLoginData(body) {
const soup = new JSSoup(body);
const forms = soup.findAll("form");
const form = forms.find((form) => form.attrs.id === "loginForm");
if (!form) {
throw new Error("LOGIN_FORM_NOT_FOUND");
}
const action = url.resolve(IDP_URL, form.attrs.action);
const inputs = {};
for (const input of form.findAll("input")) {
const name = input.attrs.name || "";
const value = input.attrs.value || "";
const namelc = name.toLowerCase();
if (namelc.includes("user")) {
inputs[name] = IDP_USER;
} else if (namelc.includes("email")) {
inputs[name] = IDP_USER;
} else if (namelc.includes("pass")) {
inputs[name] = IDP_PASS;
} else {
inputs[name] = value;
}
}
return { action, inputs };
}
function getSAMLAssertion(body) {
const soup = new JSSoup(body);
const inputs = soup.findAll("input");
const saml = inputs.find((input) => input.attrs.name === "SAMLResponse");
if (!saml) {
throw new Error("SAML_ASSERTION_NOT_FOUND");
}
return base64decode(saml.attrs.value);
}
function getSAMLRoles(saml) {
const soup = new JSSoup(saml);
const roles = soup
.findAll("AttributeValue")
.filter((value) => {
return (
value.parent amp;amp;
value.parent.name === "Attribute" amp;amp;
value.parent.attrs amp;amp;
value.parent.attrs.Name ===
"https://aws.amazon.com/SAML/Attributes/Role"
);
})
.map((value) => {
const [provider, role] = (value.text || "").split(",");
return { provider, role };
});
if (!roles[0]) {
throw new Error("SAML_ROLE_NOT_FOUND");
}
return roles;
}
async function getSTSToken(provider, role, assertion) {
const params = {
DurationSeconds: 3600,
PrincipalArn: provider,
RoleArn: role,
SAMLAssertion: base64encode(assertion),
};
return new Promise((resolve, reject) => {
sts.assumeRoleWithSAML(params, (error, data) => {
if (error) {
reject(error);
} else {
resolve(data);
}
});
});
}
function saveSTSToken(filename, profile, sts) {
const readFile = () => {
try {
return ini.decode(fs.readFileSync(filename, "utf-8"));
} catch (e) {
return {};
}
};
const writeFile = (config) => {
fs.writeFileSync(filename, ini.encode(config, { whitespace: true }));
};
const config = readFile();
const section = config[profile] || {};
const credentials = sts.Credentials || {};
section.aws_access_key_id = credentials.AccessKeyId;
section.aws_secret_access_key = credentials.SecretAccessKey;
section.aws_session_token = credentials.SessionToken;
config[profile] = section;
writeFile(config);
}
function checkUsage() {
if (!IDP_URL) {
throw new Error("IDP_URL not set!");
}
if (!IDP_USER) {
throw new Error("IDP_USER not set!");
}
if (!IDP_PASS) {
throw new Error("IDP_PASS not set!");
}
if (!AWS_PROFILE) {
throw new Error("AWS_PROFILE not set!");
}
}
(async function main() {
try {
console.log(`aws-saml-session started.`);
checkUsage();
console.log(`Logging into SAML provider...`);
const resp1 = await httpGet(IDP_URL);
const data = getLoginData(resp1.body);
// This is the place its not waiting and getting me body of the page
//where it needs to wait and get me SAML response
```const resp2 = await httpPost(data.action, data.inputs);
const saml = getSAMLAssertion(resp2.body);```
const roles = getSAMLRoles(saml);
const { provider, role } = roles[0];
console.log(`Assuming role: ${role}...`);
const sts = await getSTSToken(provider, role, saml);
console.log(`Saving credentials: ${AWS_PROFILE}...`);
saveSTSToken(CONFIG_FILE, AWS_PROFILE, sts);
console.log("Done.");
} catch (e) {
console.log("ERROR:", e.message);
}
})();
Комментарии:
1. Что вы уже пробовали? В конце концов, это серия запросов, перенаправлений и синтаксического анализа HTML. С чем-нибудь конкретным у вас возникли проблемы?
2. @AleksanderWons Я отредактировал свой вопрос с учетом проделанной до сих пор работы и места, где я застрял. Мне действительно нужно это, чтобы сделать это. Пожалуйста, помогите
3. Этот запрос завершен.
4. Не могли бы вы, пожалуйста, поделиться @PrashantGupta, как это сработало в итоге?