feat: support non-root users to log in
Support vm password login for non-root users when creating vm/ironic Change-Id: Iaf692e333686d2563013c0ea41777da8c772ce35
This commit is contained in:
parent
d02497a15d
commit
80e1d1275d
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Support non-root users can log in to VM:
|
||||||
|
|
||||||
|
* When creating a vm/ironic, log in with a password. The username is required. The username comes from the image configuration or user input. Non-root is supported.
|
@ -39,11 +39,28 @@ export class SystemStep extends Base {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get imageInfo() {
|
||||||
|
const { context = {} } = this.props;
|
||||||
|
const { image = {} } = context || {};
|
||||||
|
const { selectedRows = [] } = image;
|
||||||
|
return selectedRows.length && selectedRows[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
get loginUserName() {
|
||||||
|
return this.imageInfo?.os_admin_user;
|
||||||
|
}
|
||||||
|
|
||||||
|
get loginUserNameInContext() {
|
||||||
|
const { username = '' } = this.props.context || {};
|
||||||
|
return username || '';
|
||||||
|
}
|
||||||
|
|
||||||
get defaultValue() {
|
get defaultValue() {
|
||||||
const { context = {} } = this.props;
|
const { context = {} } = this.props;
|
||||||
const data = {
|
const data = {
|
||||||
loginType: context.loginType || this.loginTypes[0],
|
loginType: context.loginType || this.loginTypes[0],
|
||||||
more: false,
|
more: false,
|
||||||
|
username: this.loginUserName || this.loginUserNameInContext,
|
||||||
};
|
};
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
@ -71,10 +88,32 @@ export class SystemStep extends Base {
|
|||||||
return ['loginType', 'password', 'confirmPassword'];
|
return ['loginType', 'password', 'confirmPassword'];
|
||||||
}
|
}
|
||||||
|
|
||||||
get formItems() {
|
get isPassword() {
|
||||||
const { loginType } = this.state;
|
const { loginType } = this.state;
|
||||||
const isPassword = loginType === this.loginTypes[1].value;
|
return loginType === this.loginTypes[1].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get usernameFormItem() {
|
||||||
|
const item = {
|
||||||
|
name: 'username',
|
||||||
|
label: t('Login Name'),
|
||||||
|
type: 'input',
|
||||||
|
extra: this.loginUserName
|
||||||
|
? ''
|
||||||
|
: t(
|
||||||
|
"The feasible configuration of cloud-init or cloudbase-init service in the image is not synced to image's properties, so the Login Name is unknown."
|
||||||
|
),
|
||||||
|
tip: t(
|
||||||
|
'Whether the Login Name can be used is up to the feasible configuration of cloud-init or cloudbase-init service in the image.'
|
||||||
|
),
|
||||||
|
required: this.isPassword,
|
||||||
|
hidden: !this.isPassword,
|
||||||
|
};
|
||||||
|
item.disabled = !!this.loginUserName;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
get formItems() {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: 'name',
|
name: 'name',
|
||||||
@ -91,6 +130,7 @@ export class SystemStep extends Base {
|
|||||||
options: this.loginTypes,
|
options: this.loginTypes,
|
||||||
isWrappedValue: true,
|
isWrappedValue: true,
|
||||||
},
|
},
|
||||||
|
this.usernameFormItem,
|
||||||
{
|
{
|
||||||
name: 'keypair',
|
name: 'keypair',
|
||||||
label: t('Keypair'),
|
label: t('Keypair'),
|
||||||
@ -98,8 +138,8 @@ export class SystemStep extends Base {
|
|||||||
data: this.keypairs,
|
data: this.keypairs,
|
||||||
isLoading: this.keyPairStore.list.isLoading,
|
isLoading: this.keyPairStore.list.isLoading,
|
||||||
isMulti: false,
|
isMulti: false,
|
||||||
required: !isPassword,
|
required: !this.isPassword,
|
||||||
hidden: isPassword,
|
hidden: this.isPassword,
|
||||||
tip: t(
|
tip: t(
|
||||||
'The SSH key is a way to remotely log in to the instance. The cloud platform only helps to keep the public key. Please keep your private key properly.'
|
'The SSH key is a way to remotely log in to the instance. The cloud platform only helps to keep the public key. Please keep your private key properly.'
|
||||||
),
|
),
|
||||||
@ -125,16 +165,16 @@ export class SystemStep extends Base {
|
|||||||
name: 'password',
|
name: 'password',
|
||||||
label: t('Password'),
|
label: t('Password'),
|
||||||
type: 'input-password',
|
type: 'input-password',
|
||||||
required: isPassword,
|
required: this.isPassword,
|
||||||
hidden: !isPassword,
|
hidden: !this.isPassword,
|
||||||
otherRule: getPasswordOtherRule('password', 'instance'),
|
otherRule: getPasswordOtherRule('password', 'instance'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'confirmPassword',
|
name: 'confirmPassword',
|
||||||
label: t('Confirm Password'),
|
label: t('Confirm Password'),
|
||||||
type: 'input-password',
|
type: 'input-password',
|
||||||
required: isPassword,
|
required: this.isPassword,
|
||||||
hidden: !isPassword,
|
hidden: !this.isPassword,
|
||||||
otherRule: getPasswordOtherRule('confirmPassword', 'instance'),
|
otherRule: getPasswordOtherRule('confirmPassword', 'instance'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
@ -327,7 +327,10 @@ export class CreateIronic extends StepAction {
|
|||||||
server.return_reservation_id = true;
|
server.return_reservation_id = true;
|
||||||
}
|
}
|
||||||
if (server.adminPass || userData) {
|
if (server.adminPass || userData) {
|
||||||
server.user_data = btoa(getUserData(server.adminPass, userData));
|
const { username } = values;
|
||||||
|
server.user_data = btoa(
|
||||||
|
getUserData(server.adminPass, userData, username || 'root')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
server,
|
server,
|
||||||
|
@ -147,6 +147,7 @@ export class SystemStep extends Base {
|
|||||||
more: false,
|
more: false,
|
||||||
physicalNodeType: physicalNodeTypes[0],
|
physicalNodeType: physicalNodeTypes[0],
|
||||||
userData: '',
|
userData: '',
|
||||||
|
username: this.loginUserName || this.loginUserNameInContext,
|
||||||
};
|
};
|
||||||
if (servergroup) {
|
if (servergroup) {
|
||||||
data.serverGroup = {
|
data.serverGroup = {
|
||||||
@ -210,6 +211,11 @@ export class SystemStep extends Base {
|
|||||||
return this.sourceInfo && this.sourceInfo.os_admin_user;
|
return this.sourceInfo && this.sourceInfo.os_admin_user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get loginUserNameInContext() {
|
||||||
|
const { username = '' } = this.props.context || {};
|
||||||
|
return username || '';
|
||||||
|
}
|
||||||
|
|
||||||
onValuesChange = (changedFields) => {
|
onValuesChange = (changedFields) => {
|
||||||
if (has(changedFields, 'serverGroup')) {
|
if (has(changedFields, 'serverGroup')) {
|
||||||
this.onServerGroupChange(changedFields.serverGroup);
|
this.onServerGroupChange(changedFields.serverGroup);
|
||||||
@ -223,9 +229,33 @@ export class SystemStep extends Base {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
get isPassword() {
|
||||||
|
const { loginType } = this.state;
|
||||||
|
return loginType === this.loginTypes[1].value;
|
||||||
|
}
|
||||||
|
|
||||||
|
get usernameFormItem() {
|
||||||
|
const item = {
|
||||||
|
name: 'username',
|
||||||
|
label: t('Login Name'),
|
||||||
|
type: 'input',
|
||||||
|
extra: this.loginUserName
|
||||||
|
? ''
|
||||||
|
: t(
|
||||||
|
"The feasible configuration of cloud-init or cloudbase-init service in the image is not synced to image's properties, so the Login Name is unknown."
|
||||||
|
),
|
||||||
|
tip: t(
|
||||||
|
'Whether the Login Name can be used is up to the feasible configuration of cloud-init or cloudbase-init service in the image.'
|
||||||
|
),
|
||||||
|
required: this.isPassword,
|
||||||
|
hidden: !this.isPassword,
|
||||||
|
};
|
||||||
|
item.disabled = !!this.loginUserName;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
get formItems() {
|
get formItems() {
|
||||||
const { loginType, more = false, physicalNodeType } = this.state;
|
const { more = false, physicalNodeType } = this.state;
|
||||||
const isPassword = loginType === this.loginTypes[1].value;
|
|
||||||
const isManually = physicalNodeType === physicalNodeTypes[1].value;
|
const isManually = physicalNodeType === physicalNodeTypes[1].value;
|
||||||
|
|
||||||
const { initKeyPair } = this.state;
|
const { initKeyPair } = this.state;
|
||||||
@ -245,27 +275,15 @@ export class SystemStep extends Base {
|
|||||||
options: this.loginTypes,
|
options: this.loginTypes,
|
||||||
isWrappedValue: true,
|
isWrappedValue: true,
|
||||||
},
|
},
|
||||||
{
|
this.usernameFormItem,
|
||||||
name: 'username',
|
|
||||||
label: t('Login Name'),
|
|
||||||
content: this.loginUserName || '-',
|
|
||||||
extra: this.loginUserName
|
|
||||||
? ''
|
|
||||||
: t(
|
|
||||||
"The feasible configuration of cloud-init or cloudbase-init service in the image is not synced to image's properties, so the Login Name is unknown."
|
|
||||||
),
|
|
||||||
tip: t(
|
|
||||||
'Whether the Login Name can be used is up to the feasible configuration of cloud-init or cloudbase-init service in the image.'
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: 'keypair',
|
name: 'keypair',
|
||||||
label: t('Keypair'),
|
label: t('Keypair'),
|
||||||
type: 'select-table',
|
type: 'select-table',
|
||||||
data: this.keypairs,
|
data: this.keypairs,
|
||||||
isLoading: this.keyPairStore.list.isLoading,
|
isLoading: this.keyPairStore.list.isLoading,
|
||||||
required: !isPassword,
|
required: !this.isPassword,
|
||||||
hidden: isPassword,
|
hidden: this.isPassword,
|
||||||
header: getKeyPairHeader(this),
|
header: getKeyPairHeader(this),
|
||||||
initValue: initKeyPair,
|
initValue: initKeyPair,
|
||||||
tip: t(
|
tip: t(
|
||||||
@ -293,16 +311,16 @@ export class SystemStep extends Base {
|
|||||||
name: 'password',
|
name: 'password',
|
||||||
label: t('Login Password'),
|
label: t('Login Password'),
|
||||||
type: 'input-password',
|
type: 'input-password',
|
||||||
required: isPassword,
|
required: this.isPassword,
|
||||||
hidden: !isPassword,
|
hidden: !this.isPassword,
|
||||||
otherRule: getPasswordOtherRule('password', 'instance'),
|
otherRule: getPasswordOtherRule('password', 'instance'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'confirmPassword',
|
name: 'confirmPassword',
|
||||||
label: t('Confirm Password'),
|
label: t('Confirm Password'),
|
||||||
type: 'input-password',
|
type: 'input-password',
|
||||||
required: isPassword,
|
required: this.isPassword,
|
||||||
hidden: !isPassword,
|
hidden: !this.isPassword,
|
||||||
otherRule: getPasswordOtherRule('confirmPassword', 'instance'),
|
otherRule: getPasswordOtherRule('confirmPassword', 'instance'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -743,7 +743,10 @@ export class StepCreate extends StepAction {
|
|||||||
physicalNode.selectedRows[0].hypervisor_hostname;
|
physicalNode.selectedRows[0].hypervisor_hostname;
|
||||||
}
|
}
|
||||||
if (server.adminPass || userData) {
|
if (server.adminPass || userData) {
|
||||||
server.user_data = btoa(getUserData(server.adminPass, userData));
|
const { username } = values;
|
||||||
|
server.user_data = btoa(
|
||||||
|
getUserData(server.adminPass, userData, username || 'root')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const body = {
|
const body = {
|
||||||
server,
|
server,
|
||||||
|
@ -218,7 +218,7 @@ const passwordAndUserData =
|
|||||||
'Content-Disposition: attachment; filename="passwd-script.txt" \n' +
|
'Content-Disposition: attachment; filename="passwd-script.txt" \n' +
|
||||||
'\n' +
|
'\n' +
|
||||||
'#!/bin/sh\n' +
|
'#!/bin/sh\n' +
|
||||||
"echo 'root:USER_PASSWORD' | chpasswd\n" +
|
"echo 'USER_NAME:USER_PASSWORD' | chpasswd\n" +
|
||||||
'\n' +
|
'\n' +
|
||||||
'--===============2309984059743762475==\n' +
|
'--===============2309984059743762475==\n' +
|
||||||
'Content-Type: text/x-shellscript; charset="us-ascii" \n' +
|
'Content-Type: text/x-shellscript; charset="us-ascii" \n' +
|
||||||
@ -252,7 +252,7 @@ const onlyPassword =
|
|||||||
'Content-Disposition: attachment; filename="passwd-script.txt" \n' +
|
'Content-Disposition: attachment; filename="passwd-script.txt" \n' +
|
||||||
'\n' +
|
'\n' +
|
||||||
'#!/bin/sh\n' +
|
'#!/bin/sh\n' +
|
||||||
"echo 'root:USER_PASSWORD' | chpasswd\n" +
|
"echo 'USER_NAME:USER_PASSWORD' | chpasswd\n" +
|
||||||
'\n' +
|
'\n' +
|
||||||
'--===============2309984059743762475==--';
|
'--===============2309984059743762475==--';
|
||||||
|
|
||||||
@ -270,13 +270,15 @@ const onlyUserData =
|
|||||||
'\n' +
|
'\n' +
|
||||||
'--===============2309984059743762475==--';
|
'--===============2309984059743762475==--';
|
||||||
|
|
||||||
export const getUserData = (password, userData) => {
|
export const getUserData = (password, userData, username = 'root') => {
|
||||||
if (password && userData) {
|
if (password && userData) {
|
||||||
const str = passwordAndUserData.replace(/USER_PASSWORD/g, password);
|
let str = passwordAndUserData.replace(/USER_PASSWORD/g, password);
|
||||||
|
str = str.replace(/USER_NAME/g, username);
|
||||||
return str.replace(/USER_DATA/g, userData);
|
return str.replace(/USER_DATA/g, userData);
|
||||||
}
|
}
|
||||||
if (password) {
|
if (password) {
|
||||||
return onlyPassword.replace(/USER_PASSWORD/g, password);
|
const str = onlyPassword.replace(/USER_PASSWORD/g, password);
|
||||||
|
return str.replace(/USER_NAME/g, username);
|
||||||
}
|
}
|
||||||
return onlyUserData.replace(/USER_DATA/g, userData);
|
return onlyUserData.replace(/USER_DATA/g, userData);
|
||||||
};
|
};
|
||||||
|
@ -58,6 +58,7 @@ describe('The Instance Page', () => {
|
|||||||
.clickStepActionNextButton()
|
.clickStepActionNextButton()
|
||||||
.formInput('name', name)
|
.formInput('name', name)
|
||||||
.formRadioChoose('loginType', 1)
|
.formRadioChoose('loginType', 1)
|
||||||
|
.formInput('username', 'root')
|
||||||
.formInput('password', password)
|
.formInput('password', password)
|
||||||
.formInput('confirmPassword', password)
|
.formInput('confirmPassword', password)
|
||||||
.wait(2000)
|
.wait(2000)
|
||||||
|
@ -66,6 +66,7 @@ onlyOn(ironicServiceEnabled, () => {
|
|||||||
.clickStepActionNextButton()
|
.clickStepActionNextButton()
|
||||||
.formInput('name', name)
|
.formInput('name', name)
|
||||||
.formRadioChoose('loginType', 1)
|
.formRadioChoose('loginType', 1)
|
||||||
|
.formInput('username', 'root')
|
||||||
.formInput('password', password)
|
.formInput('password', password)
|
||||||
.formInput('confirmPassword', password)
|
.formInput('confirmPassword', password)
|
||||||
.wait(2000)
|
.wait(2000)
|
||||||
|
@ -62,6 +62,7 @@ describe('The Server Group Page', () => {
|
|||||||
.clickStepActionNextButton()
|
.clickStepActionNextButton()
|
||||||
.formInput('name', instanceName)
|
.formInput('name', instanceName)
|
||||||
.formRadioChoose('loginType', 1)
|
.formRadioChoose('loginType', 1)
|
||||||
|
.formInput('username', 'root')
|
||||||
.formInput('password', password)
|
.formInput('password', password)
|
||||||
.formInput('confirmPassword', password)
|
.formInput('confirmPassword', password)
|
||||||
.clickStepActionNextButton()
|
.clickStepActionNextButton()
|
||||||
|
@ -48,6 +48,7 @@ Cypress.Commands.add('createInstance', ({ name, networkName }) => {
|
|||||||
.clickStepActionNextButton()
|
.clickStepActionNextButton()
|
||||||
.formInput('name', name)
|
.formInput('name', name)
|
||||||
.formRadioChoose('loginType', 1)
|
.formRadioChoose('loginType', 1)
|
||||||
|
.formInput('username', 'root')
|
||||||
.formInput('password', password)
|
.formInput('password', password)
|
||||||
.formInput('confirmPassword', password)
|
.formInput('confirmPassword', password)
|
||||||
.wait(2000)
|
.wait(2000)
|
||||||
@ -159,6 +160,7 @@ Cypress.Commands.add(
|
|||||||
.clickStepActionNextButton()
|
.clickStepActionNextButton()
|
||||||
.formInput('name', name)
|
.formInput('name', name)
|
||||||
.formRadioChoose('loginType', 1)
|
.formRadioChoose('loginType', 1)
|
||||||
|
.formInput('username', 'root')
|
||||||
.formInput('password', password)
|
.formInput('password', password)
|
||||||
.formInput('confirmPassword', password)
|
.formInput('confirmPassword', password)
|
||||||
.clickStepActionNextButton()
|
.clickStepActionNextButton()
|
||||||
|
Loading…
Reference in New Issue
Block a user