fleet/frontend/pages/hosts/ManageHostsPage/components/AddHostModal/AddHostModal.jsx
Martavis Parker bcfac603f0
Added components to Storybook library (#2768)
* added storybook

* added avatar component

* added button story

* added dropdown button story

* removed unused ellipsis component

* cleaned up modal path

* reorganized enroll secrets table file

* added flash story; removed unused persistent flash

* added fleet ace story

* added checkbox story

* added dropdown story

* added input story

* fixed storybook build

* fixed avatar

* added input with icon story

* added radio button story

* added select targets dropdown story

* added slider story

* added tooltip story

* added info banner story

* removed unused loaders; added spinner story

* added modal story

* removed unused NumberPill

* added pagination story

* lint fixes

* added documentation to run

* modified documentation

* fixed corelayout test

* fixed format for date-fns

* fixed date format that breaks tests

* wait for page
2021-11-06 23:41:09 -07:00

373 lines
11 KiB
JavaScript

import React, { Component } from "react";
import PropTypes from "prop-types";
import FileSaver from "file-saver";
import Fleet from "fleet";
import Button from "components/buttons/Button";
import configInterface from "interfaces/config";
import teamInterface from "interfaces/team";
import userInterface from "interfaces/user";
import permissionUtils from "utilities/permissions";
import EnrollSecretTable from "components/EnrollSecretTable";
import FleetIcon from "components/icons/FleetIcon";
import Dropdown from "components/forms/fields/Dropdown";
import InputField from "components/forms/fields/InputField";
import { stringToClipboard } from "utilities/copy_text";
import DownloadIcon from "../../../../../../assets/images/icon-download-12x12@2x.png";
const baseClass = "add-host-modal";
const NO_TEAM_OPTION = {
value: "no-team",
label: "No team",
};
class AddHostModal extends Component {
static propTypes = {
teams: PropTypes.arrayOf(teamInterface),
onReturnToApp: PropTypes.func,
config: configInterface,
currentUser: userInterface,
};
constructor(props) {
super(props);
this.userRole = {
isAnyTeamMaintainer: permissionUtils.isAnyTeamMaintainer(
this.props.currentUser
),
isGlobalAdmin: permissionUtils.isGlobalAdmin(this.props.currentUser),
isGlobalMaintainer: permissionUtils.isGlobalMaintainer(
this.props.currentUser
),
};
this.currentUserTeams = this.userRole.isAnyTeamMaintainer
? Object.values(this.props.currentUser.teams).filter(
(team) => team.role === "maintainer"
)
: this.props.teams;
this.teamSecrets = this.props.teams
? Object.values(this.props.teams).map((team) => {
return { id: team.id, name: team.name, secrets: team.secrets };
})
: [];
this.state = {
fetchCertificateError: undefined,
selectedTeam: null,
globalSecrets: [],
selectedEnrollSecrets: [],
copyMessage: "",
};
}
componentDidMount() {
const { isGlobalAdmin, isGlobalMaintainer } = this.userRole;
(() => {
if (isGlobalAdmin || isGlobalMaintainer) {
Fleet.config
.loadEnrollSecret()
.then((response) => {
this.setState({
globalSecrets: response.spec.secrets,
selectedTeam: { id: NO_TEAM_OPTION.value }, // Reset initial selectedTeam value to "no-team" in the case of global users
});
})
.catch((err) => {
console.log(err);
});
} else {
this.setState({ selectedTeam: this.currentUserTeams[0] });
}
})();
Fleet.config
.loadCertificate()
.then((certificate) => {
this.setState({ certificate });
})
.catch(() => {
this.setState({
fetchCertificateError:
"Failed to load certificate. Is Fleet app URL configured properly?",
});
});
}
onFetchCertificate = (evt) => {
evt.preventDefault();
const { certificate } = this.state;
const filename = "fleet.pem";
const file = new global.window.File([certificate], filename, {
type: "application/x-pem-file",
});
FileSaver.saveAs(file);
return false;
};
onCopyRunCommand = (evt) => {
evt.preventDefault();
stringToClipboard("osqueryd --flagfile=flagfile.txt --verbose")
.then(() => this.setState({ copyMessage: "Copied!" }))
.catch(() => this.setState({ copyMessage: "Copy failed" }));
// Clear message after 1 second
setTimeout(() => this.setState({ copyMessage: "" }), 1000);
return false;
};
// if isGlobalAdmin or isGlobalMaintainer, we include a "No team" option and reveal globalSecrets
// if not, we pull secrets for the user's teams from the teamsSecrets
onChangeSelectTeam = (teamId) => {
const { globalSecrets } = this.state;
const { currentUserTeams, teamSecrets } = this;
if (teamId === "no-team") {
this.setState({ selectedTeam: { id: NO_TEAM_OPTION.value } });
this.setState({ selectedEnrollSecrets: globalSecrets || [] });
} else {
const selectedTeam = currentUserTeams.find((team) => team.id === teamId);
const selectedEnrollSecrets =
teamSecrets.find((e) => e.id === selectedTeam.id)?.secrets || "";
this.setState({ selectedTeam });
this.setState({
selectedEnrollSecrets,
});
}
};
getSelectedEnrollSecrets = (selectedTeam) => {
if (selectedTeam.id === NO_TEAM_OPTION.value) {
return this.state.globalSecrets;
}
return (
this.teamSecrets.find((e) => e.id === selectedTeam.id)?.secrets || ""
);
};
createTeamDropdownOptions = (currentUserTeams = []) => {
const teamOptions = currentUserTeams.map((team) => {
return {
value: team.id,
label: team.name,
};
});
return this.userRole.isAnyTeamMaintainer
? teamOptions
: [NO_TEAM_OPTION, ...teamOptions];
};
renderRunCommandLabel = () => {
const { copyMessage } = this.state;
const { onCopyRunCommand } = this;
return (
<span className={`${baseClass}__name`}>
<span className="buttons">
{copyMessage && <span>{`${copyMessage} `}</span>}
<Button
variant="unstyled"
className={`${baseClass}__run-osquery-copy-icon`}
onClick={onCopyRunCommand}
>
<FleetIcon name="clipboard" />
</Button>
</span>
</span>
);
};
render() {
const { config, onReturnToApp } = this.props;
const { fetchCertificateError, selectedTeam, globalSecrets } = this.state;
const {
createTeamDropdownOptions,
currentUserTeams,
getSelectedEnrollSecrets,
onChangeSelectTeam,
renderRunCommandLabel,
} = this;
const isPremiumTier = permissionUtils.isPremiumTier(config);
let tlsHostname = config.server_url;
try {
const serverUrl = new URL(config.server_url);
tlsHostname = serverUrl.hostname;
if (serverUrl.port) {
tlsHostname += `:${serverUrl.port}`;
}
} catch (e) {
if (!(e instanceof TypeError)) {
throw e;
}
}
const flagfileContent = `# Server
--tls_hostname=${tlsHostname}
--tls_server_certs=fleet.pem
# Enrollment
--host_identifier=instance
--enroll_secret_path=secret.txt
--enroll_tls_endpoint=/api/v1/osquery/enroll
# Configuration
--config_plugin=tls
--config_tls_endpoint=/api/v1/osquery/config
--config_refresh=10
# Live query
--disable_distributed=false
--distributed_plugin=tls
--distributed_interval=10
--distributed_tls_max_attempts=3
--distributed_tls_read_endpoint=/api/v1/osquery/distributed/read
--distributed_tls_write_endpoint=/api/v1/osquery/distributed/write
# Logging
--logger_plugin=tls
--logger_tls_endpoint=/api/v1/osquery/log
--logger_tls_period=10
# File carving
--disable_carver=false
--carver_start_endpoint=/api/v1/osquery/carve/begin
--carver_continue_endpoint=/api/v1/osquery/carve/block
--carver_block_size=2000000`;
const onDownloadFlagfile = (evt) => {
evt.preventDefault();
const filename = "flagfile.txt";
const file = new global.window.File([flagfileContent], filename);
FileSaver.saveAs(file);
return false;
};
return (
<div className={baseClass}>
<div className={`${baseClass}__manual-install-content`}>
<div className={`${baseClass}__documentation-link`}>
<h4>
<a
href="https://github.com/fleetdm/fleet/blob/2f42c281f98e39a72ab4a5125ecd26d303a16a6b/docs/1-Using-Fleet/4-Adding-hosts.md"
target="_blank"
rel="noopener noreferrer"
>
Add Hosts Documentation <FleetIcon name="external-link" />
</a>
</h4>
</div>
<ol className={`${baseClass}__install-steps`}>
<li>
<h4>
<span className={`${baseClass}__step-number`}>1</span>Enroll
secret
</h4>
<p>
Osquery uses an enroll secret to authenticate with the Fleet
server.
</p>
<div className={`${baseClass}__secret-wrapper`}>
{isPremiumTier && currentUserTeams ? (
<Dropdown
wrapperClassName={`${baseClass}__team-dropdown-wrapper`}
label={"Select a team for this new host:"}
value={selectedTeam && selectedTeam.id}
options={createTeamDropdownOptions(currentUserTeams)}
onChange={onChangeSelectTeam}
placeholder={"Select a team"}
searchable={false}
/>
) : null}
{isPremiumTier && selectedTeam && (
<EnrollSecretTable
secrets={getSelectedEnrollSecrets(selectedTeam)}
/>
)}
{!isPremiumTier && (
<EnrollSecretTable secrets={globalSecrets} />
)}
</div>
</li>
<li>
<h4>
<span className={`${baseClass}__step-number`}>2</span>Server
certificate
</h4>
<p>
Provide the TLS certificate used by the Fleet server to enable
secure connections from osquery:
</p>
<p>
{fetchCertificateError ? (
<span className={`${baseClass}__error`}>
{fetchCertificateError}
</span>
) : (
<a
href="#downloadCertificate"
onClick={this.onFetchCertificate}
>
Download
<img src={DownloadIcon} alt="download icon" />
</a>
)}
</p>
</li>
<li>
<h4>
<span className={`${baseClass}__step-number`}>3</span>Flagfile
</h4>
<p>
If using the enroll secret and server certificate downloaded
above, use the generated flagfile. In some configurations,
modifications may need to be made:
</p>
<p>
<a href="#downloadFlagfile" onClick={onDownloadFlagfile}>
Download
<img src={DownloadIcon} alt="download icon" />
</a>
</p>
</li>
<li>
<h4>
<span className={`${baseClass}__step-number`}>4</span>Run
osquery
</h4>
<p>
Run osquery from the directory containing the above files (may
require sudo or Run as Administrator privileges):
</p>
<div>
<InputField
disabled
inputWrapperClass={`${baseClass}__run-osquery-input`}
name="run-osquery"
label={renderRunCommandLabel()}
type={"text"}
value={"osqueryd --flagfile=flagfile.txt --verbose"}
/>
</div>
</li>
</ol>
</div>
<div className={`${baseClass}__button-wrap`}>
<Button onClick={onReturnToApp} className="button button--brand">
Done
</Button>
</div>
</div>
);
}
}
export default AddHostModal;