[] = [{
39 | target: "data",
40 | eventHandlers: {
41 | onClick: () => {
42 | return [{
43 | target: "data",
44 | mutation: (props) => {
45 | window.open(baseUrl + props.datum.status + "§ion=" + props.datum.x);
46 | return null
47 | }
48 | }];
49 | }
50 | }
51 | }]
52 |
53 | return (
54 |
55 | `${datum.y} ${datum.status} responses for ${datum.x} (${sectionNames[datum.x]})`} constrainToVisibleArea />}
59 | legendData={statuses.map((status) => { return { name: status }})}
60 | legendOrientation="vertical"
61 | legendPosition="right"
62 | height={275}
63 | theme={customTheme(statuses, 'stack')}
64 | padding={{
65 | bottom: 50, // Adjusted to accommodate legend
66 | left: 50,
67 | right: 140,
68 | top: 10
69 | }}
70 | width={700}
71 | >
72 |
73 |
74 | StatusColor[status])}>
75 | { data.map((statusData) => { return ( ) } ) }
79 |
80 |
81 |
82 | )
83 | })
84 |
85 |
86 |
--------------------------------------------------------------------------------
/assets/src/app/ato/Charts/CompletionCharts.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { PageSection } from '@patternfly/react-core';
3 | import { Tabs, Tab } from '@patternfly/react-core';
4 | import { ComponentStats } from '@app/ato/Charts/common'
5 | import { CompletionPieCharts} from '@app/ato/Charts/PieCharts'
6 | import { CompletionStackCharts } from '@app/ato/Charts/StackCharts'
7 | import { CompletionRadarCharts } from '@app/ato/Charts/RadarCharts'
8 | import { CompletionBarCharts } from '@app/ato/Charts/BarCharts'
9 | import * as Api from '@app/lib/api'
10 | import * as qs from '@app/lib/querystring'
11 |
12 | interface CompletionChartsProps {
13 | productId: string;
14 | }
15 |
16 | interface CompletionChartsState {
17 | activeTabKey: number;
18 | productId: string;
19 | data: ComponentStats;
20 | }
21 |
22 | const titleToId = {
23 | undefined: 0,
24 | "0": 0,
25 | "1": 1,
26 | "2": 2,
27 | "3": 3,
28 | }
29 |
30 | export class CompletionCharts extends React.PureComponent {
31 | constructor(props) {
32 | super(props);
33 | const params = qs.Parse()
34 | this.state = {
35 | activeTabKey: titleToId[params.tab],
36 | data: {},
37 | productId: props.productId,
38 |
39 | };
40 | this.handleTabClick = this.handleTabClick.bind(this);
41 | this.reloadData()
42 | }
43 |
44 | handleTabClick(event, tabIndex){
45 | qs.Set({'tab': tabIndex})
46 | this.setState({
47 | activeTabKey: tabIndex
48 | });
49 | };
50 |
51 | static getDerivedStateFromProps(props, state) {
52 | if (state.productId != props.productId) {
53 | return {productId: props.productId, data: {}}
54 | }
55 | return null;
56 | }
57 |
58 | componentDidUpdate() {
59 | if (Object.keys(this.state.data).length == 0 && this.state.productId != 'select') {
60 | this.reloadData()
61 | }
62 | }
63 |
64 | reloadData() {
65 | Api.statistics(this.state.productId).then(data => {this.setState({data: data})})
66 | }
67 |
68 | render() {
69 | return (
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | );
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/assets/src/app/ato/Charts/PieCharts.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { TextContent, Text } from '@patternfly/react-core';
3 | import { ChartPie } from '@patternfly/react-charts';
4 | import { CompletionChartProps, CompletionChartsProps, controlsBaseUrl, customTheme } from '@app/ato/Charts/common'
5 |
6 | export const CompletionPieCharts = React.memo((props: CompletionChartsProps) => {
7 | const { data } = props;
8 | return (
9 |
10 | { Object.keys(data).map((c) => { return (
11 |
12 |
13 | {data[c].Certification}
14 |
15 |
16 |
17 | )}) }
18 |
19 | )
20 | })
21 |
22 | const CertificationCompletionPieChart = React.memo((props: CompletionChartProps) => {
23 | const res = props.cs.History[props.cs.History.length - 1].Stats
24 | const data = Object.keys(res).map((c) => {
25 | return {"x": c, "y": res[c]}
26 | })
27 | const legend = Object.keys(res).map((c) => {
28 | return {"name": c + ": " + res[c]}
29 | })
30 | const baseUrl = controlsBaseUrl(props.cs.Certification)
31 | return (
32 |
33 | `${datum.x}: ${datum.y}`}
40 | legendData={legend}
41 | legendOrientation="vertical"
42 | legendPosition="right"
43 | events={[{
44 | target: "data",
45 | eventHandlers: {
46 | onClick: () => {
47 | return [{
48 | target: "data",
49 | mutation: (props) => {
50 | window.open(baseUrl + data[props.index].x);
51 | return null
52 | }
53 | }];
54 | }
55 | }
56 | }]}
57 | padding={{
58 | bottom: 20,
59 | left: 20,
60 | right: 180, // Adjusted to accommodate legend
61 | top: 20
62 | }}
63 | theme={customTheme(data.map((s) => s.x) , 'pie')}
64 | width={350}
65 | />
66 |
67 | )
68 | })
69 |
--------------------------------------------------------------------------------
/assets/src/app/ato/Charts/RadarCharts.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { TextContent, Text } from '@patternfly/react-core';
3 | import { CompletionChartProps, CompletionChartsProps, controlsBaseUrl, statusSort, sectionNames } from '@app/ato/Charts/common';
4 | import { VictoryChart, VictoryBar, VictoryPolarAxis, VictoryStack, VictoryTheme, VictoryTooltip } from 'victory';
5 | import { StatusColor } from '@app/ato/Products/DataList';
6 | import { VictoryBarTTargetType } from 'victory-bar';
7 | import { EventPropTypeInterface } from 'victory-core';
8 |
9 | export const CompletionRadarCharts = React.memo((props: CompletionChartsProps) => {
10 | const { data } = props;
11 | return (
12 |
13 | { Object.keys(data).map((c) => { return (
14 |
15 |
16 | {data[c].Certification}
17 |
18 |
19 |
20 | )}) }
21 |
22 | )
23 | })
24 |
25 | const CompletionRadarChart = React.memo((props: CompletionChartProps) => {
26 | const pf = props.cs.PerFamily;
27 | const statuses = statusSort(Object.keys(pf).map((family) => Object.keys(pf[family])).reduce((a, b) => a.concat(b)).filter((value, index, self) => {
28 | return self.indexOf(value) === index;
29 | }))
30 | const controlsPerFamily = Object.keys(pf).reduce((map, family) => {
31 | map[family] =Object.keys(pf[family]).map((status) => pf[family][status]).reduce((a, b) => a + b);
32 | return map;
33 | }, {})
34 | const data = statuses.map((status) => {
35 | return Object.keys(pf).map((family) => {
36 | var y = pf[family][status]
37 | y = y == undefined ? 0 : y
38 | return { x: family, y: y / controlsPerFamily[family], status: status, count: y }
39 | });
40 | })
41 | const baseUrl = controlsBaseUrl(props.cs.Certification)
42 | const eventHandlers: EventPropTypeInterface[] = [{
43 | target: "data",
44 | eventHandlers: {
45 | onClick: () => {
46 | return [{
47 | target: "data",
48 | mutation: (props) => {
49 | window.open(baseUrl + props.datum.status + "§ion=" + props.datum.x);
50 | return null
51 | }
52 | }];
53 | }
54 | }
55 | }]
56 |
57 | return (
58 |
59 |
62 | {
63 | Object.keys(pf).map((d, i) => {
64 | return (
65 |
72 | );
73 | })
74 | }
75 |
76 | StatusColor[status]) }
78 | style={{ data: { width: 20} }}
79 | >
80 | { data.map((statusData) => { return ( `${datum.count} ${datum.status} responses for ${datum.x} (${sectionNames[datum.x]})`}
82 |
83 | labelComponent={ }
84 | events={eventHandlers}
85 | key={statusData[0].x} data={statusData} />) } ) }
86 |
87 |
88 |
89 | );
90 | })
91 |
--------------------------------------------------------------------------------
/assets/src/app/ato/Charts/StackCharts.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { TextContent, Text } from '@patternfly/react-core';
3 | import { ChartVoronoiContainer, ChartAreaProps } from '@patternfly/react-charts';
4 | import { CompletionChartProps, CompletionChartsProps, controlsBaseUrl, customTheme } from '@app/ato/Charts/common'
5 | import { Chart, ChartArea, ChartAxis, ChartStack } from '@patternfly/react-charts';
6 | import { VictoryAreaTTargetType } from 'victory-area';
7 | import { EventPropTypeInterface } from 'victory-core';
8 |
9 | export const CompletionStackCharts = React.memo((props: CompletionChartsProps) => {
10 | const { data } = props;
11 | return (
12 |
13 | { Object.keys(data).map((c) => { return (
14 |
15 |
16 | {data[c].Certification}
17 |
18 |
19 |
20 | )}) }
21 |
22 | )
23 | })
24 |
25 | const CompletionStackChart = React.memo((props: CompletionChartProps) => {
26 | const statuses = props.cs.History.map((s) => Object.keys(s.Stats)).reduce((a, b) => a.concat(b)).filter((value, index, self) => {
27 | return self.indexOf(value) === index;
28 | })
29 |
30 | const result = statuses.map((status) => {
31 | return props.cs.History.map((snapshot, k) => {
32 | const y = snapshot.Stats[status]
33 | return { 'name': status, 'x': new Date(snapshot.Time), 'y': y == undefined ? 0 : y }
34 | })
35 | })
36 |
37 | const legendData = statuses.map((status) => {
38 | return { name: status }
39 | })
40 | const maxDomain = Object.values(props.cs.History[0].Stats).reduce((a, b) => { return (a as number) + (b as number) }) as number
41 | const baseUrl = controlsBaseUrl(props.cs.Certification)
42 | const eventHandlers: EventPropTypeInterface[] = [{
43 | target: "data",
44 | eventHandlers: {
45 | onClick: () => {
46 | return [{
47 | target: "data",
48 | mutation: (props) => {
49 | window.open(baseUrl + props.data[0].name);
50 | return null
51 | }
52 | }];
53 | }
54 | }
55 | }]
56 |
57 | return (
58 |
59 | `${datum.name}: ${datum.y}`} constrainToVisibleArea />}
76 | >
77 |
78 |
79 |
80 | { result.map((statusArea) => {
81 | return ( )
82 | }) }
83 |
84 |
85 |
86 | );
87 | })
88 |
--------------------------------------------------------------------------------
/assets/src/app/ato/Charts/common.tsx:
--------------------------------------------------------------------------------
1 | import { ChartThemeColor, getTheme, ChartThemeVariant } from '@patternfly/react-charts';
2 | import { StatusColor } from '@app/ato/Products/DataList'
3 |
4 | export interface CompletionChartsProps {
5 | data: ComponentStats;
6 | }
7 |
8 | export interface CompletionChartProps {
9 | cs: CertificationStats;
10 | }
11 |
12 | export function controlsBaseUrl(standardName: string) {
13 | return window.location.pathname.replace('/Charts', '/NIST-800-53') + "?standard=" + standardName + "&status="
14 | }
15 |
16 | export function customTheme(statuses: any[], chartType: string) {
17 | const theme = getTheme(ChartThemeColor.blue, ChartThemeVariant.light)
18 | statuses.forEach((status, i) => {
19 | theme[chartType].colorScale[i] = StatusColor[status]
20 | if (theme.legend) {
21 | theme.legend!.colorScale![i] = StatusColor[status]
22 | }
23 | })
24 | return theme
25 | }
26 |
27 | export type ComponentStats = {[certID: string]: CertificationStats}
28 |
29 | export interface CertificationStats {
30 | Certification: string;
31 | History: ResultSnapshot[];
32 | PerFamily: {[FamilyId: string]: ControlResponses};
33 | }
34 |
35 | interface ResultSnapshot {
36 | Time: string;
37 | Stats: ControlResponses;
38 | }
39 |
40 | type ControlResponses = {[CtrlId: string]: number};
41 |
42 | const statusOrder = {
43 | 'complete': 1,
44 | 'not applicable': 2,
45 | 'partial': 3,
46 | 'planned': 4,
47 | 'unsatisfied': 5,
48 | 'unknown': 6,
49 | 'none': 7,
50 | }
51 |
52 | export function statusSort(statuses: string[]) {
53 | return statuses.sort((a, b) => {
54 | return statusOrder[a] - statusOrder[b]
55 | })
56 | }
57 |
58 | export const sectionNames = {
59 | 'AC': 'Access Control',
60 | 'AT': 'Awareness and Training',
61 | 'AU': 'Audit and Accountability',
62 | 'CA': 'Security Assessment & Authorization',
63 | 'CM': 'Configuration Management',
64 | 'CP': 'Contingency Planning',
65 | 'IA': 'Identification and Authentication',
66 | 'IR': 'Incident Response',
67 | 'MA': 'Maintenance',
68 | 'MP': 'Media Protection',
69 | 'PE': 'Physical & Environmental Protection',
70 | 'PL': 'Planning',
71 | 'PS': 'Personnel Security',
72 | 'RA': 'Risk Management',
73 | 'SA': 'System and Services Acquisition',
74 | 'SC': 'Systems and Communications Protection',
75 | 'SI': 'System and Information Integrity',
76 | };
77 |
--------------------------------------------------------------------------------
/assets/src/app/ato/Documents/Documents.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Page, PageSection, PageSectionVariants } from '@patternfly/react-core';
3 | import { Flex, FlexItem } from '@patternfly/react-core';
4 |
5 | import VMP from '@app/assets/markdown/vulnerability-management-plan.md';
6 | import TP from '@app/assets/markdown/training-plan.md';
7 | import { Markdown } from '@app/lib/markdown';
8 | import { ComponentSSPTemplates } from '@app/ato/Documents/SSPTemplates';
9 |
10 | const Document: React.FunctionComponent = (props) => {
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 | {props.children}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | )
29 | }
30 |
31 | const ATOVulnerabilityManagementPlan: React.FunctionComponent = (props) => {
32 | return ( );
33 | }
34 |
35 | const ATOTrainingPlan: React.FunctionComponent = (props) => {
36 | return ( );
37 | }
38 |
39 | export { ATOVulnerabilityManagementPlan, ATOTrainingPlan, ComponentSSPTemplates };
40 |
--------------------------------------------------------------------------------
/assets/src/app/ato/Documents/Overview.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { PageSection, PageSectionVariants } from '@patternfly/react-core';
3 | import { Page,
4 | Card,
5 | CardBody,
6 | CardHeader,
7 | Gallery,
8 | GalleryItem,
9 | TextContent,
10 | Text,
11 | } from '@patternfly/react-core';
12 | import { NavLink } from 'react-router-dom';
13 | import { BookReaderIcon, GhostIcon } from '@patternfly/react-icons';
14 |
15 | const ATODocuments: React.FunctionComponent = (props) => {
16 | return (
17 |
18 |
19 |
20 | ATO Documents
21 | A&A Templates
22 |
23 | Red Hat Public Sector is in the process of open sourcing ATO artifacts.
24 | Developed with US Government agencies and system integrators, these templates are
25 | tailored for the operation of Red Hat technologies and complement
26 | organizational-level policies and procedures.
27 |
28 | Many of these documents are undergoing prepublication review for open source release.
29 | As that happens they will be posted here!
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | Vulnerability Management
42 |
43 |
44 |
45 | The vulnerability management process begins with vulnerabilities being identified or reported to Red Hat’s Product Security team.
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | Security Awareness
56 |
57 |
58 |
59 | This resource has been compiled to assist with Security Awareness Training requirements.
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | );
69 | }
70 |
71 | export { ATODocuments };
72 |
--------------------------------------------------------------------------------
/assets/src/app/ato/Documents/SSPTemplates.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | Flex, FlexItem,
3 | Page, PageSection, PageSectionVariants,
4 | Text, TextContent
5 | } from '@patternfly/react-core';
6 | import { FileAltIcon, FileCodeIcon, FileWordIcon } from '@patternfly/react-icons';
7 | import { Table, TableBody, TableHeader, TableVariant } from '@patternfly/react-table';
8 | import * as React from 'react';
9 |
10 | interface FedRAMPsProps {
11 | productID: string;
12 | }
13 |
14 | class ComponentSSPTemplates extends React.Component {
15 | columns = ['FedRAMP Low', 'FedRAMP Moderate', 'FedRAMP High'];
16 |
17 | buildRows(product: string) {
18 | return [
19 | [
20 | this.FedRAMPLink(product, "Low", "docx"),
21 | this.FedRAMPLink(product, "Moderate", "docx"),
22 | this.FedRAMPLink(product, "High", "docx"),
23 | ],
24 | [
25 | this.FedRAMPLink(product, "Low", "xml"),
26 | this.FedRAMPLink(product, "Moderate", "xml"),
27 | this.FedRAMPLink(product, "High", "xml"),
28 | ],
29 | [
30 | this.FedRAMPLink(product, "Low", "json"),
31 | this.FedRAMPLink(product, "Moderate", "json"),
32 | this.FedRAMPLink(product, "High", "json"),
33 | ]
34 | ]
35 | }
36 |
37 | private FedRAMPLink(product: string, _level: string, _format: string) {
38 | var icon =
39 |
40 | switch (_format) {
41 | case "xml":
42 | icon = ;
43 | break;
44 | case "docx":
45 | icon = ;
46 | break;
47 | }
48 | var oscal = _format != "docx"
49 |
50 | return (
51 |
52 | {icon} {oscal ? ".oscal" : ""}.{_format}
53 |
54 | );
55 | }
56 |
57 | render() {
58 | // const textMod = {modifier: 'flex-2' as 'flex-default', breakpoint: 'md' as 'md'};
59 | // const emptyMod = {modifier: 'flex-1' as 'flex-default', breakpoint: 'md' as 'md'};
60 | return (
61 |
62 |
63 |
64 |
65 |
66 |
67 | SSP Templates
68 |
69 | The FedRAMP templates are dynamically generated using the FedRAMP Templater tool, originally created by GSA's 18F and then rewritten to adopt OSCAL. An automated build system incorporates Red Hat's OpenControl Content directly into the FedRAMP Templates provided by the GSA FedRAMP PMO .
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
88 |
89 |
90 |
91 | )
92 | }
93 | }
94 |
95 | export { ComponentSSPTemplates };
96 |
--------------------------------------------------------------------------------
/assets/src/app/ato/GettingStarted.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import pic from 'assets/src/app/assets/images/RMF-steps-all.png';
3 | import { Brand, Flex, FlexItem, PageSection, PageSectionVariants, TextListVariants } from '@patternfly/react-core';
4 | import {
5 | Page, TextContent, Text, TextList, TextListItem, TextVariants,
6 | } from '@patternfly/react-core';
7 |
8 | const GettingStarted: React.FunctionComponent = () => {
9 | return (
10 |
11 |
12 |
13 | {/* Getting Started */}
14 | Getting Started
15 |
16 | Red Hat’s ATO Pathways was created to provide resources that help accelerate your ATO (Authority/Authorization To Operate) process. This is made possible by combining open source projects that were spearheaded by Red Hat and still actively maintained in order to give our customers the necessary content needed for our products that help assist in the overall development of an ATO.
17 |
18 |
19 | {/* What Exactly is an ATO? */}
20 | What Exactly is an ATO?
21 |
22 | At a high level, as defined by the National Institute of Standards and Technology (NIST), an ATO is:
23 |
24 | ... an official management decision given by a senior organizational official to authorize operation of an information system and to explicitly accept the risk to organizational operations (including mission, functions, image, or reputation), organizational assets, individuals, other organizations, and the Nation based on the implementation of an agreed-upon set of security controls.
25 |
26 |
27 |
28 | It's your agency authority certifying your system is ready for day 2 operations: system availability, routine maintenance, monitoring, and lots of other concerns.
29 |
30 |
31 | The ATO process can be lengthy and daunting if not familiar with it. This process is formally defined by NIST’s Risk Management Framework (RMF) and consists of the following steps:
32 |
33 |
34 | {/* ATO PROCESS Picture + Ordered List */}
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | Prepare
43 | Categorize Information System
44 | Select Security Controls
45 | Implement Security Controls
46 | Assess Security Controls
47 | Authorize Information System
48 | Monitor Security Controls
49 |
50 |
51 |
52 |
53 |
54 | {/* How does ATO Pathways Help? */}
55 | How does ATO Pathways Help?
56 |
57 | In order to better serve our customers who seek to attain an ATO with our products, we felt it was necessary to provide general guidance to the NIST 800-53 catalog of security and privacy controls. These internal evaluations help not only to provide the control responses for NIST 800-53, but also helps drive our engineering efforts in ensuring we are complying with these controls.
58 |
59 |
60 | On top of providing control responses for products, we also provide Security Content Automation Protocol (SCAP) content generated from our ComplianceAsCode project to assist with scanning and remediation of information systems.
61 |
62 |
63 | We’ve also introduced a new feature that allow you the ability to generate FedRAMP templates dynamically based on the product chosen. Again, the idea behind this is to help consumers get a head start on the ATO process.
64 |
65 |
66 | {/* Looking Ahead */}
67 | Looking Ahead
68 |
69 | More NIST 800-53 product assessments to be completed are on the horizon. We are planning on providing some role-based guides to assist consumers in finding the right content depending on their responsibilities.
70 |
71 |
72 | As always, your feedback and/or contributions is most important. There are many projects in which you can help (see the about button in the upper right corner for more information).
73 |
74 |
75 | {/* Role-based guides
76 |
77 |
78 | Auditors
79 |
80 | Content to assist with system accreditation based on the NIST Risk Management Framework. Materials include Security Requirement Traceability Matrixes, product certification materials (Common Criteria, FIPS), and other ATO package documents.
81 |
82 |
83 |
84 | Administrators
85 |
86 | Resources needed to implement Red Hat technologies in accordance with Government security regulations. Materials include Ansible playbooks, SCAP datastreams, kickstart files, and supporting documentation.
87 |
88 |
89 |
90 | Security Professionals
91 |
92 | You are a security specialist that helps define security requirements and has a deep understanding of how to analyze systems for security threats and vulnerabilities.
93 |
94 |
95 |
96 | Developers/Application Administrators
97 |
98 | You are a developer that builds software and systems that must be hardened to a security compliance baseline. Your goal is to understand early in the process what the security engineering you will need to implement to be compliant.
99 |
100 |
101 | */}
102 |
103 |
104 |
105 | );
106 | }
107 |
108 | export { GettingStarted };
109 |
--------------------------------------------------------------------------------
/assets/src/app/ato/Products/Product.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import {
3 | Alert,
4 | ApplicationLauncher, ApplicationLauncherItem,
5 | DropdownPosition,
6 | Page, PageSection, PageSectionVariants,
7 | TextContent,
8 | Text,
9 | PageHeader,
10 | } from '@patternfly/react-core';
11 | import { Spinner } from '@patternfly/react-core';
12 | import { EditAltIcon } from '@patternfly/react-icons'
13 |
14 | import * as Api from '@app/lib/api'
15 | import { Markdown } from '@app/lib/markdown';
16 | import { ProductIdOverride, ProductInfo } from '@app/ato/Products/Static.tsx'
17 | import { RTMDataList } from '@app/ato/Products/DataList.tsx'
18 | import { Products } from '@app/ato/Products/Products'
19 | import { Certification } from '@app/lib/opencontrol'
20 | import { CompletionCharts } from '@app/ato/Charts/CompletionCharts'
21 | import { ComponentSSPTemplates } from '@app/ato/Documents/SSPTemplates'
22 |
23 | interface ProposeChangeProps {
24 | link: string;
25 | }
26 |
27 | interface ProposeChangeState {
28 | isOpen: boolean;
29 | }
30 |
31 | class ProposeChange extends React.PureComponent {
32 | constructor(props) {
33 | super(props);
34 | this.state = {
35 | isOpen: false
36 | };
37 | this.onSelect = this.onSelect.bind(this);
38 | this.onToggle = this.onToggle.bind(this);
39 |
40 | }
41 |
42 | onSelect(event) {
43 | this.setState({
44 | isOpen: !this.state.isOpen
45 | });
46 | };
47 | onToggle(isOpen) {
48 | this.setState({
49 | isOpen
50 | });
51 | };
52 |
53 | render() {
54 | const { isOpen } = this.state;
55 | const appLauncherItems = [
56 |
57 | Propose changes to this page
58 | ,
59 | ];
60 | return (
61 |
66 | );
67 | }
68 | }
69 |
70 | interface ProductState {
71 | isLoading: boolean;
72 | productId: string;
73 | product: any;
74 | certifications: Certification[];
75 | activeTabKey: string;
76 | };
77 |
78 | class Product extends React.PureComponent {
79 | static texts(productId: string) {
80 | return ProductInfo[productId] ? ProductInfo[productId].texts : {};
81 | }
82 |
83 | texts() {
84 | return Product.texts(this.state.productId);
85 | }
86 |
87 | renderMarkdown(textId) {
88 | const texts = this.texts();
89 | const Element = texts[textId] as any;
90 | if (Element) {
91 | return
92 |
93 |
94 | }
95 | return '';
96 | }
97 |
98 | showingMarkdown(): boolean {
99 | return this.state.activeTabKey != 'NIST-800-53' && this.state.activeTabKey != 'nist-800-53' && !this.showingCharts() && !this.showingSSPTemplates()
100 | }
101 |
102 | showingSSPTemplates(): boolean {
103 | return this.state.activeTabKey == 'ssp-templates'
104 | }
105 |
106 | showingCharts(): boolean {
107 | return this.state.activeTabKey == 'Charts'
108 | }
109 |
110 | urlForEditing(): string {
111 | if (this.showingMarkdown()) {
112 | return "https://github.com/RedHatGov/ocdb/edit/master/assets/src/app/assets/markdown/products/" + this.state.productId + "/" + this.state.activeTabKey + ".md"
113 | } else {
114 | return "https://github.com/ComplianceAsCode/redhat/tree/master/" + this.state.productId
115 | }
116 | }
117 |
118 | renderTabs() {
119 | if (this.showingMarkdown()) {
120 | const renderMarkdown = this.renderMarkdown;
121 | return renderMarkdown(this.state.activeTabKey);
122 | } else if (this.state.product != null && this.state.product.controls.length == 0) {
123 | return (
124 | NIST-800-53 responses for {this.state.product.name} are not available in a form of open controls and/or oscal.
125 | )
126 | } else if (this.showingSSPTemplates()) {
127 | return this.renderSSPTemplates()
128 | } else if (this.showingCharts()) {
129 | return this.renderCharts()
130 | } else {
131 | if (this.state.isLoading) {
132 | return
133 | } else {
134 | return (
135 |
136 |
137 | {this.state.product['errors'].length == 0 ?
138 | '' :
139 |
140 | OpenControls Developer Information
141 |
142 | {this.state.product['errors'].map((function (error, i) {
143 | return {error} ;
144 | }))}
145 |
146 |
147 | }
148 | Requirements Traceability Matrix
149 |
150 |
151 |
152 | )
153 | }
154 | }
155 | }
156 |
157 | render() {
158 | if (this.state.productId == 'select') {
159 | return
160 | }
161 |
162 | // const Header = (
163 | //
166 | // );
167 | while (this.state.isLoading) {
168 | return (
169 |
170 |
171 |
172 |
173 |
174 |
175 | )
176 | }
177 | return (
178 | }>
183 |
184 |
185 |
186 | {this.renderTabs()}
187 |
188 |
189 |
190 | );
191 | }
192 |
193 | renderCharts() {
194 | return (
195 |
196 | )
197 | }
198 |
199 | renderSSPTemplates() {
200 | return (
201 |
202 | )
203 | }
204 |
205 | static getId(props) {
206 | return ProductIdOverride(props['computedMatch'].params.productId);
207 | }
208 | static getDerivedStateFromProps(props, state) {
209 | const productId = Product.getId(props);
210 | const activeTabKey = props['computedMatch'].params.tabId;
211 | if (state.productId != productId) {
212 | return {
213 | isLoading: true,
214 | productId: productId,
215 | product: null,
216 | activeTabKey: activeTabKey,
217 | }
218 | }
219 | if (state.activeTabKey != activeTabKey) {
220 | return { activeTabKey };
221 | }
222 | return null;
223 | }
224 | componentDidUpdate() {
225 | if (this.state.isLoading && this.state.productId != 'select') {
226 | Api.componentControls(this.state.productId)
227 | .then(data => {
228 | this.setState({ product: data, isLoading: false })
229 | })
230 | }
231 | }
232 |
233 | constructor(props) {
234 | super(props);
235 | const productId = Product.getId(props);
236 |
237 | this.state = {
238 | isLoading: true,
239 | productId: productId,
240 | product: null,
241 | certifications: [],
242 | activeTabKey: props['computedMatch'].params.tabId,
243 | };
244 | this.renderMarkdown = this.renderMarkdown.bind(this);
245 | this.componentDidUpdate = this.componentDidUpdate.bind(this);
246 | Api.certifications().then(data => this.setState({ certifications: data }))
247 | this.componentDidUpdate();
248 | }
249 | }
250 |
251 | export { Product };
252 |
--------------------------------------------------------------------------------
/assets/src/app/ato/Products/Products.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Brand, CardBody, PageSection, PageSectionVariants } from '@patternfly/react-core';
3 | import {
4 | Page,
5 | Gallery,
6 | GalleryItem,
7 | TextContent,
8 | Text,
9 | Card,
10 | CardTitle,
11 | CardFooter,
12 | CardHeader,
13 | CardHeaderMain,
14 | } from '@patternfly/react-core';
15 | import { Spinner } from '@patternfly/react-core';
16 | import { NavLink } from 'react-router-dom';
17 | import { GetProductParamsFromUrl } from '@app/AppLayout/ProductSelector'
18 | import { ProductInfo } from '@app/ato/Products/Static.tsx'
19 |
20 | import * as Api from '@app/lib/api'
21 | import redhatLogo from '@app/assets/images/rh-hat.png';
22 |
23 | const ProductGalleryItem: React.FunctionComponent = (props) => {
24 | var productId = props['product']['key'];
25 | var logo = (ProductInfo[productId] && ProductInfo[productId].image) || redhatLogo;
26 | return (
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | {/* {props['product']['name']} */}
36 | {props['product']['name']}
37 | { props.product.satisfies !== undefined ?
38 | {props['product']['satisfies'].length} controls defined
39 | : "" }
40 |
41 |
42 |
43 | )
44 | };
45 |
46 | class Products extends React.Component {
47 | render(){
48 | const params = GetProductParamsFromUrl();
49 | return (
50 |
51 |
52 |
53 | Products
54 | Select a product to view its security-relevant documentation.
55 |
56 |
57 |
58 |
59 |
60 | { (this.state['isLoading'] ? : this.state['products'].map((function(object, i){
61 | return ( );
62 | })))}
63 |
64 |
65 |
66 | );
67 | }
68 |
69 | constructor(props) {
70 | super(props);
71 |
72 | this.state = {
73 | isLoading: true,
74 | products: []
75 | };
76 | Api.components()
77 | .then(data => this.setState({products: data, isLoading: false}));
78 | }
79 | }
80 |
81 | export { Products };
82 |
83 |
--------------------------------------------------------------------------------
/assets/src/app/ato/Products/RTMDetail.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { PageSection, TextContent, Text, Tooltip, TooltipPosition } from '@patternfly/react-core';
3 | import { InfoAltIcon } from '@patternfly/react-icons'
4 | import MDX from '@mdx-js/runtime'
5 | import { CustomControl } from '@app/lib/opencontrol'
6 | import { SatisfiesAccordion } from '@app/ato/Products/SatisfiesAccordion.tsx'
7 |
8 | interface RTMDetailProps {
9 | control: CustomControl;
10 | }
11 |
12 | const RTMDetail = React.memo((props: RTMDetailProps) => {
13 | const parsed = props.control.Key.split(')')[0].split(' (');
14 | const key = parsed[0]
15 | const enhancement = parsed[1]
16 | var c = props.control;
17 |
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | {c.Key}: {c.Control.name}
29 | {c.Control.description}
30 | {c.Key}: What is the solution and how is it implemented?
31 |
32 |
33 |
34 | );
35 | })
36 |
37 | export { RTMDetail }
38 |
--------------------------------------------------------------------------------
/assets/src/app/ato/Products/SatisfiesAccordion.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { ExpandableSection, Text } from '@patternfly/react-core';
3 | import MDX from '@mdx-js/runtime'
4 | import { Satisfies } from '@app/lib/opencontrol'
5 |
6 | interface CustomControlProps {
7 | satisfies?: Satisfies;
8 | }
9 |
10 | const SatisfiesAccordion = React.memo((props: CustomControlProps) => {
11 | if (props.satisfies == null || (props.satisfies.narrative.length == 1 && props.satisfies.narrative[0].text == '')) {
12 | return (Not available );
13 | }
14 | if (props.satisfies.narrative.length == 1 && props.satisfies.narrative[0].key == undefined) {
15 | return ({props.satisfies.narrative[0].text} )
16 | }
17 |
18 | const cKey = props.satisfies.control_key;
19 | return (
20 |
21 | { props.satisfies.narrative.map(function(n, idx) {
22 | return (
23 |
24 | {n.text}
25 |
26 |
27 | )
28 | })}
29 |
30 | )
31 | })
32 |
33 | export { SatisfiesAccordion }
34 |
--------------------------------------------------------------------------------
/assets/src/app/ato/Products/Static.tsx:
--------------------------------------------------------------------------------
1 | export const ProductIdOverridesMap: {[Identifier: string]: string} = {
2 | coreos4: 'coreos-4',
3 | idm: 'identity-management',
4 | ocp3: 'openshift-container-platform-3',
5 | osp13: 'openstack-platform-13',
6 | rhvh: 'virtualization-host',
7 | rhvm: 'virtualization-manager'
8 | }
9 |
10 | function ProductIdOverride(id) {
11 | // Previously our product content was published under different names.
12 | // If user refers to the product by old name, we fetch the new name first.
13 | return ProductIdOverridesMap[id] || id;
14 | }
15 |
16 |
17 | interface ProductText {
18 | name: string;
19 | text: any;
20 | }
21 |
22 | export interface ProductTemplate {
23 | image?: string;
24 | texts: {[Identifier: string]:ProductText};
25 | };
26 |
27 | const ProductInfo: {[Identifier: string]: ProductTemplate } = {
28 | 'ansible-tower': {
29 | image: require('@app/assets/images/rh-tower.png').default,
30 | texts: {
31 | 'Overview': require('@app/assets/markdown/products/ansible-tower/Overview.md').default,
32 | 'ICS-500-27': require('@app/assets/markdown/products/ansible-tower/ICS-500-27.md').default,
33 | 'SCAP': require('@app/assets/markdown/products/ansible-tower/SCAP.md').default,
34 | }
35 | },
36 | 'coreos-4': {
37 | image: require('@app/assets/images/rh-coreos.png').default,
38 | texts: {
39 | 'Overview': require('@app/assets/markdown/products/coreos-4/Overview.md').default,
40 | 'SCAP': require('@app/assets/markdown/products/coreos-4/SCAP.md').default,
41 | }
42 | },
43 | 'identity-management': {
44 | texts: {
45 | 'Overview': require('@app/assets/markdown/products/identity-management/Overview.md').default
46 | }
47 | },
48 | insights: {
49 | texts: {
50 | 'Overview': require('@app/assets/markdown/products/insights/Overview.md').default
51 | }
52 | },
53 | 'openshift-container-platform-3': {
54 | image: require('@app/assets/images/rh-openshift.png').default,
55 | texts: {
56 | 'Overview': require('@app/assets/markdown/products/openshift-container-platform-3/Overview.md').default,
57 | 'SCAP': require('@app/assets/markdown/products/openshift-container-platform-3/SCAP.md').default,
58 | }
59 | },
60 | 'openshift-container-platform-4': {
61 | image: require('@app/assets/images/rh-openshift.png').default,
62 | texts: {
63 | 'Overview': require('@app/assets/markdown/products/openshift-container-platform-4/Overview.md').default,
64 | 'SCAP': require('@app/assets/markdown/products/openshift-container-platform-4/SCAP.md').default,
65 | }
66 | },
67 | 'openshift-dedicated': {
68 | image: require('@app/assets/images/rh-openshift.png').default,
69 | texts: {
70 | 'Overview': require('@app/assets/markdown/products/openshift-dedicated/Overview.md').default
71 | }
72 | },
73 | 'openstack-platform-13': {
74 | image: require('@app/assets/images/rh-openstack.png').default,
75 | texts: {
76 | 'Overview': require('@app/assets/markdown/products/openstack-platform-13/Overview.md').default,
77 | 'SCAP': require('@app/assets/markdown/products/openstack-platform-13/SCAP.md').default,
78 | }
79 | },
80 | 'rhacm': {
81 | texts: {
82 | 'Overview': require('@app/assets/markdown/products/rhacm/Overview.md').default,
83 | }
84 | },
85 | 'rhel-7': {
86 | image: require('@app/assets/images/rh-rhel.png').default,
87 | texts: {
88 | 'Overview': require('@app/assets/markdown/products/rhel-7/Overview.md').default,
89 | 'SCAP': require('@app/assets/markdown/products/rhel-7/SCAP.md').default,
90 | }
91 | },
92 | 'rhel-8': {
93 | image: require('@app/assets/images/rh-rhel8.png').default,
94 | texts: {
95 | 'Overview': require('@app/assets/markdown/products/rhel-8/Overview.md').default,
96 | 'SCAP': require('@app/assets/markdown/products/rhel-8/SCAP.md').default,
97 | 'STIG': require('@app/assets/markdown/products/rhel-8/STIG.md').default,
98 | }
99 | },
100 | 'virtualization-host': {
101 | image: require('@app/assets/images/rh-virtualization.png').default,
102 | texts: {
103 | 'SCAP': require('@app/assets/markdown/products/virtualization-host/SCAP.md').default,
104 | 'Overview': require('@app/assets/markdown/products/virtualization-host/Overview.md').default,
105 | }
106 | },
107 | 'virtualization-manager': {
108 | image: require('@app/assets/images/rh-virtualization.png').default,
109 | texts: {
110 | 'Overview': require('@app/assets/markdown/products/virtualization-manager/Overview.md').default,
111 | }
112 | }
113 | };
114 |
115 | export { ProductIdOverride, ProductInfo }
116 |
--------------------------------------------------------------------------------
/assets/src/app/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import '@patternfly/react-core/dist/styles/base.css';
3 | import { BrowserRouter as Router } from 'react-router-dom';
4 | import { AppLayout } from '@app/AppLayout/AppLayout';
5 | import { AppRoutes } from '@app/routes';
6 | import DocumentTitle from 'react-document-title';
7 | import '@app/app.css';
8 |
9 | const App: React.FunctionComponent = () => {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | );
19 | };
20 |
21 | export { App };
22 |
--------------------------------------------------------------------------------
/assets/src/app/lib/Memoize.tsx:
--------------------------------------------------------------------------------
1 | export const Memoize = (fn) => {
2 | let cache = {};
3 | return (...args) => {
4 | let n = args[0]; // just taking one argument here
5 | if (n in cache) {
6 | return cache[n];
7 | }
8 | else {
9 | let result = fn(n);
10 | cache[n] = result;
11 | return result;
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/assets/src/app/lib/api.tsx:
--------------------------------------------------------------------------------
1 | function memoize(method) {
2 | let cache = {};
3 | return async function(...argum) {
4 | let args = JSON.stringify(argum);
5 | cache[args] = cache[args] || method.apply(undefined, argum);
6 | return cache[args];
7 | };
8 | }
9 |
10 | export var components = memoize(async function components() {
11 | return fetch('/api/v1/components').then(response => response.json()).then(addExtraProducts)
12 | });
13 |
14 | const extraProducts = [
15 | ];
16 |
17 | function addExtraProducts(products) {
18 | return new Promise(
19 | function(resolve, reject) {
20 | resolve(extraProducts.concat(products).sort(
21 | function(a, b){
22 | return a['name'] < b['name'] ? -1 : 1
23 | }))
24 | }
25 | )
26 | }
27 |
28 | export var componentControls = memoize(async function(componentId: string) {
29 | return fetch('/api/v1/components/' + componentId + '/controls')
30 | .then(response => response.json())
31 | .then((data) => {
32 | const nist80053 = data['controls']['NIST-800-53'];
33 | data['controls'] = Array.prototype.concat.apply([], Object.keys(nist80053).map(function(k, _) { return nist80053[k]; }));
34 | return data
35 | })
36 | });
37 |
38 | export var certifications = memoize(async function() {
39 | return fetch('/api/v1/certifications')
40 | .then(response => response.json())
41 | .then(data => {
42 | return Array.prototype.concat.apply([], Object.keys(data).map(function(k, _) {
43 | return {Key: k,
44 | Controls: data[k].Controls['NIST-800-53']}
45 | }))
46 | })
47 | })
48 |
49 | export var statistics = memoize(async function(componentId: string) {
50 | return fetch('/api/v1/components/' + componentId + '/statistics')
51 | .then(response => response.json())
52 | })
53 |
--------------------------------------------------------------------------------
/assets/src/app/lib/csv.tsx:
--------------------------------------------------------------------------------
1 | export function ServeCSV(filename, data) {
2 | var blob = new Blob([data], {type: 'text/csv'});
3 | if (false) {
4 | window.navigator.msSaveBlob(blob, filename);
5 | }
6 | else{
7 | var elem = window.document.createElement('a');
8 | elem.href = window.URL.createObjectURL(blob);
9 | elem.download = filename;
10 | document.body.appendChild(elem);
11 | elem.click();
12 | document.body.removeChild(elem);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/assets/src/app/lib/markdown.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { MDXProvider } from '@mdx-js/react'
3 | import { TextContent } from '@patternfly/react-core';
4 |
5 | const pfTable = (props) =>
6 |
7 | const pfMapping = {
8 | table: pfTable,
9 | }
10 |
11 | const Markdown: React.FunctionComponent = (props) => {
12 | return (
13 |
14 |
15 | {props.children}
16 |
17 |
18 | )
19 | }
20 |
21 | export { Markdown };
22 |
--------------------------------------------------------------------------------
/assets/src/app/lib/opencontrol.tsx:
--------------------------------------------------------------------------------
1 | interface Narrative {
2 | key?: string,
3 | text: string
4 | }
5 |
6 | export interface Satisfies {
7 | control_key: string;
8 | narrative: Narrative[];
9 | implementation_status: string;
10 | }
11 |
12 | interface Control {
13 | name: string,
14 | description: string,
15 | }
16 |
17 | export interface CustomControl {
18 | Key: string,
19 | Control: Control,
20 | Satisfies?: Satisfies,
21 | }
22 |
23 | export interface Certification {
24 | Key: string,
25 | Controls: string[]
26 | }
27 |
28 | export function OpenControlToCSV(list: CustomControl[]) {
29 | var result = "NIST-800-53,Name,Description,Implementation Status,Control Response\n"
30 | list.map((c) => {
31 | const status = c.Satisfies ? c.Satisfies.implementation_status : "unknown"
32 | const response = c.Satisfies ?
33 | (Array.isArray(c.Satisfies.narrative) ?
34 | c.Satisfies.narrative.map((n) => { return n.text + '\n'}).reduce((a,b) => a+b) :
35 | c.Satisfies.narrative
36 | ) :
37 | ""
38 | const line = c.Key + ","
39 | + c.Control.name + ","
40 | + escape(c.Control.description) + ","
41 | + status + ","
42 | + escape(response) + "\n"
43 | result += line
44 | })
45 | return result
46 | }
47 |
48 | function escape(field) {
49 | return '"' + field.replace('"', '""') + '"'
50 | }
51 |
--------------------------------------------------------------------------------
/assets/src/app/lib/querystring.tsx:
--------------------------------------------------------------------------------
1 | import queryString from 'query-string'
2 |
3 | export function Parse() {
4 | return queryString.parse(window.location.search)
5 | }
6 |
7 | export function Set(hash) {
8 | var qs = queryString.stringify(hash)
9 | if (qs != "") {
10 | qs = "?" + qs
11 | }
12 | history.pushState({page: 1}, "title 1", window.location.pathname + qs)
13 | }
14 |
--------------------------------------------------------------------------------
/assets/src/app/routes.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import { Route, RouteComponentProps, Switch, Redirect } from 'react-router-dom';
3 | import { accessibleRouteChangeHandler } from '@app/utils/utils';
4 | import { ATODocuments } from '@app/ato/Documents/Overview';
5 | import { ATOTrainingPlan, ATOVulnerabilityManagementPlan, ComponentSSPTemplates } from '@app/ato/Documents/Documents';
6 | import { Product } from '@app/ato/Products/Product';
7 | import { Products } from '@app/ato/Products/Products';
8 | import { GettingStarted } from '@app/ato/GettingStarted';
9 | import { NotFound } from '@app/NotFound/NotFound';
10 | import DocumentTitle from 'react-document-title';
11 | import { LastLocationProvider, useLastLocation } from 'react-router-last-location';
12 | import { ProductIdOverridesMap } from '@app/ato/Products/Static'
13 | let routeFocusTimer: number;
14 |
15 |
16 | const RouteWithTitleUpdates = ({ component: Component, isAsync = false, title, ...rest }) => {
17 | const lastNavigation = useLastLocation();
18 |
19 | function routeWithTitle(routeProps: RouteComponentProps) {
20 | return (
21 |
22 |
23 |
24 | );
25 | }
26 |
27 | React.useEffect(() => {
28 | if (!isAsync && lastNavigation !== null) {
29 | routeFocusTimer = accessibleRouteChangeHandler();
30 | }
31 | return () => {
32 | clearTimeout(routeFocusTimer);
33 | };
34 | }, []);
35 |
36 | return ;
37 | };
38 |
39 | export interface IAppRoute {
40 | label: string;
41 | component: React.ComponentType> | React.ComponentType;
42 | icon: any;
43 | exact?: boolean;
44 | path: string;
45 | title: string;
46 | isAsync?: boolean;
47 | }
48 |
49 | const routes: IAppRoute[] = [
50 | {
51 | component: GettingStarted,
52 | exact: true,
53 | icon: null,
54 | label: 'Getting Started',
55 | path: '/',
56 | title: 'Getting Started',
57 | },
58 | {
59 | component: GettingStarted,
60 | exact: true,
61 | icon: null,
62 | label: 'Getting Started',
63 | path: '/ato/getting_started',
64 | title: 'Getting Started',
65 | },
66 | {
67 | component: ATODocuments,
68 | exact: true,
69 | icon: null,
70 | label: 'ATO Documents',
71 | path: '/ato/documents',
72 | title: 'ATO Documents',
73 | },
74 | {
75 | component: ATOVulnerabilityManagementPlan,
76 | exact: true,
77 | icon: null,
78 | label: 'Vulnerability Management Plan',
79 | path: '/ato/documents/vulnerability-management-plan',
80 | title: 'Vulnerability Management Plan',
81 | },
82 | {
83 | component: ATOTrainingPlan,
84 | exact: true,
85 | icon: null,
86 | label: 'Security Awareness and Training Plan',
87 | path: '/ato/documents/security-awareness-and-training-plan',
88 | title: 'Security Awareness and Training Plan',
89 | },
90 | {
91 | component: Products,
92 | exact: true,
93 | icon: null,
94 | isAsync: true,
95 | label: 'Products',
96 | path: '/ato/products',
97 | title: 'Product Documents'
98 | },
99 | {
100 | component: Product,
101 | exact: true,
102 | icon: null,
103 | isAsync: true,
104 | label: 'Product',
105 | path: '/ato/products/:productId/:tabId?',
106 | title: 'Product Document',
107 | },
108 | {
109 | component: Product,
110 | exact: true,
111 | icon: null,
112 | isAsync: true,
113 | label: 'Product',
114 | path: '/product-documents/:productId/:tabId/:garbage/', // Maintain the same links that lead to the old site.
115 | title: 'Product Document',
116 | },
117 | ];
118 |
119 | const AppRoutes = () => (
120 |
121 |
122 | {routes.map(({ path, exact, component, title, isAsync, icon }, idx) => (
123 |
132 | ))}
133 |
134 | {
135 | Object.keys(ProductIdOverridesMap).map((function(key, i) {
136 | return (
137 | }
139 | />
140 | )
141 | }))
142 | }
143 |
144 |
145 |
146 | );
147 |
148 | export { AppRoutes, routes };
149 |
--------------------------------------------------------------------------------
/assets/src/app/utils/utils.ts:
--------------------------------------------------------------------------------
1 | export function accessibleRouteChangeHandler() {
2 | return window.setTimeout(() => {
3 | const mainContainer = document.getElementById('primary-app-container');
4 | if (mainContainer) {
5 | mainContainer.focus();
6 | }
7 | }, 50);
8 | }
9 |
--------------------------------------------------------------------------------
/assets/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import { App } from '@app/index';
4 |
5 | /*
6 | if (process.env.NODE_ENV !== "production") {
7 | // tslint:disable-next-line
8 | const axe = require("react-axe");
9 | axe(React, ReactDOM, 1000);
10 | }
11 | */
12 |
13 | window.onload = function(){
14 | ReactDOM.render( , document.getElementById("root") as HTMLElement);
15 | };
16 |
--------------------------------------------------------------------------------
/assets/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.png';
2 | declare module '*.jpg';
3 | declare module '*.jpeg';
4 | declare module '*.gif';
5 | declare module '*.svg';
6 | declare module '*.css';
7 | declare module '*.wav';
8 | declare module '*.mp3';
9 | declare module '*.m4a';
10 | declare module '*.rdf';
11 | declare module '*.ttl';
12 | declare module '*.pdf';
13 | declare module 'react-document-title';
14 | declare module '*.md' {
15 | let MDXComponent: (props: any) => JSX.Element
16 | export default MDXComponent
17 | }
18 |
--------------------------------------------------------------------------------
/config/buffalo-app.toml:
--------------------------------------------------------------------------------
1 | name = "ocdb"
2 | bin = "bin/ocdb"
3 | vcs = "git"
4 | with_pop = false
5 | with_sqlite = false
6 | with_dep = false
7 | with_webpack = true
8 | with_nodejs = true
9 | with_yarn = true
10 | with_docker = true
11 | with_grifts = true
12 | as_web = true
13 | as_api = false
14 |
--------------------------------------------------------------------------------
/config/buffalo-plugins.toml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedHatGov/ocdb/23d941ac48d1f789d309def511a1910d07fdf2ee/config/buffalo-plugins.toml
--------------------------------------------------------------------------------
/database.yml:
--------------------------------------------------------------------------------
1 | ---
2 | development:
3 | dialect: postgres
4 | database: ocdb_development
5 | user: postgres
6 | password: postgres
7 | host: 127.0.0.1
8 | pool: 5
9 |
10 | test:
11 | url: {{envOr "TEST_DATABASE_URL" "postgres://postgres:postgres@127.0.0.1:5432/ocdb_test?sslmode=disable"}}
12 |
13 | production:
14 | url: {{envOr "DATABASE_URL" "postgres://postgres:postgres@127.0.0.1:5432/ocdb_production?sslmode=disable"}}
15 |
--------------------------------------------------------------------------------
/fixtures/sample.toml:
--------------------------------------------------------------------------------
1 | [[scenario]]
2 | name = "lots of widgets"
3 |
4 | [[scenario.table]]
5 | name = "widgets"
6 |
7 | [[scenario.table.row]]
8 | id = "<%= uuidNamed("widget") %>"
9 | name = "This is widget #1"
10 | body = "some widget body"
11 | created_at = "<%= now() %>"
12 | updated_at = "<%= now() %>"
13 |
14 | [[scenario.table.row]]
15 | id = "<%= uuid() %>"
16 | name = "This is widget #2"
17 | body = "some widget body"
18 | created_at = "<%= now() %>"
19 | updated_at = "<%= now() %>"
20 |
21 | [[scenario.table]]
22 | name = "users"
23 |
24 | [[scenario.table.row]]
25 | id = "<%= uuid() %>"
26 | name = "Mark Bates"
27 | admin = true
28 | age = 41
29 | widget_id = "<%= uuidNamed("widget") %>"
30 | created_at = "<%= now() %>"
31 | updated_at = "<%= now() %>"
32 |
33 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/RedHatGov/ocdb
2 |
3 | go 1.13
4 |
5 | require (
6 | github.com/Masterminds/semver/v3 v3.1.1 // indirect
7 | github.com/fatih/color v1.10.0 // indirect
8 | github.com/gobuffalo/buffalo v0.16.19
9 | github.com/gobuffalo/envy v1.9.0
10 | github.com/gobuffalo/fizz v1.13.0 // indirect
11 | github.com/gobuffalo/mw-csrf v1.0.0
12 | github.com/gobuffalo/mw-i18n v1.1.0
13 | github.com/gobuffalo/mw-paramlogger v1.0.0
14 | github.com/gobuffalo/nulls v0.4.0 // indirect
15 | github.com/gobuffalo/packr/v2 v2.8.1
16 | github.com/gobuffalo/pop/v5 v5.3.1 // indirect
17 | github.com/gobuffalo/suite v2.8.2+incompatible
18 | github.com/gobuffalo/validate/v3 v3.3.0 // indirect
19 | github.com/gocomply/scap v0.1.1 // indirect
20 | github.com/gofrs/uuid v4.0.0+incompatible // indirect
21 | github.com/google/go-cmp v0.5.2 // indirect
22 | github.com/gorilla/handlers v1.5.1 // indirect
23 | github.com/gorilla/mux v1.8.0 // indirect
24 | github.com/gorilla/sessions v1.2.1 // indirect
25 | github.com/jackc/pgproto3/v2 v2.0.7 // indirect
26 | github.com/jackc/pgx/v4 v4.10.1 // indirect
27 | github.com/karrick/godirwalk v1.16.1 // indirect
28 | github.com/konsorten/go-windows-terminal-sequences v1.0.3 // indirect
29 | github.com/lib/pq v1.8.0 // indirect
30 | github.com/markbates/grift v1.5.0
31 | github.com/microcosm-cc/bluemonday v1.0.4 // indirect
32 | github.com/monoculum/formam v0.0.0-20200923020755-6f187e4ffe27 // indirect
33 | github.com/opencontrol/compliance-masonry v1.1.7-0.20200827173050-70bb3370161e
34 | github.com/pelletier/go-toml v1.8.1 // indirect
35 | github.com/rogpeppe/go-internal v1.6.2 // indirect
36 | github.com/rolieup/golie v0.2.1
37 | github.com/sirupsen/logrus v1.7.0 // indirect
38 | github.com/spf13/cobra v1.1.1 // indirect
39 | github.com/stretchr/testify v1.6.1 // indirect
40 | golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
41 | golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
42 | golang.org/x/sync v0.0.0-20201207232520-09787c993a3a // indirect
43 | golang.org/x/sys v0.0.0-20210105210732-16f7687f5001 // indirect
44 | golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect
45 | golang.org/x/text v0.3.5 // indirect
46 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
47 | gopkg.in/yaml.v2 v2.4.0 // indirect
48 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect
49 | vbom.ml/util v0.0.3 // indirect
50 | )
51 |
52 | replace (
53 | github.com/codegangsta/cli => github.com/urfave/cli v1.18.1-0.20160712171436-3a5216227e14
54 | github.com/go-utils/ufs => ./vendor/github.com/opencontrol/fedramp-templater/vendor/github.com/go-utils/ufs
55 | github.com/go-utils/ugo => ./vendor/github.com/opencontrol/fedramp-templater/vendor/github.com/go-utils/ugo
56 | github.com/go-utils/uslice => ./vendor/github.com/opencontrol/fedramp-templater/vendor/github.com/go-utils/uslice
57 | github.com/go-utils/ustr => ./vendor/github.com/opencontrol/fedramp-templater/vendor/github.com/go-utils/ustr
58 | github.com/opencontrol/compliance-masonry/commands/docs => ./vendor/github.com/opencontrol/fedramp-templater/vendor/github.com/opencontrol/compliance-masonry/commands/docs
59 | github.com/opencontrol/compliance-masonry/config => ./vendor/github.com/opencontrol/fedramp-templater/vendor/github.com/opencontrol/compliance-masonry/config
60 | github.com/opencontrol/compliance-masonry/models => ./vendor/github.com/opencontrol/fedramp-templater/vendor/github.com/opencontrol/compliance-masonry/models
61 | github.com/opencontrol/fedramp-templater => ./vendor/github.com/opencontrol/fedramp-templater
62 | gopkg.in/fatih/set.v0 => ./vendor/github.com/opencontrol/fedramp-templater/vendor/github.com/opencontrol/compliance-masonry/vendor/gopkg.in/fatih/set.v0
63 | vbom.ml/util => github.com/fvbommel/util v0.0.0-20180919145318-efcd4e0f9787
64 | )
65 |
--------------------------------------------------------------------------------
/grifts/db.go:
--------------------------------------------------------------------------------
1 | package grifts
2 |
3 | import (
4 | "github.com/markbates/grift/grift"
5 | )
6 |
7 | var _ = grift.Namespace("db", func() {
8 |
9 | grift.Desc("seed", "Seeds a database")
10 | grift.Add("seed", func(c *grift.Context) error {
11 | // Add DB seeding stuff here
12 | return nil
13 | })
14 |
15 | })
16 |
--------------------------------------------------------------------------------
/grifts/init.go:
--------------------------------------------------------------------------------
1 | package grifts
2 |
3 | import (
4 | "github.com/gobuffalo/buffalo"
5 | "github.com/RedHatGov/ocdb/actions"
6 | )
7 |
8 | func init() {
9 | buffalo.Grifts(actions.App())
10 | }
11 |
--------------------------------------------------------------------------------
/inflections.json:
--------------------------------------------------------------------------------
1 | {
2 | "singular": "plural"
3 | }
4 |
--------------------------------------------------------------------------------
/locales/all.en-us.yaml:
--------------------------------------------------------------------------------
1 | # For more information on using i18n see: https://github.com/nicksnyder/go-i18n
2 | - id: welcome_greeting
3 | translation: "OpenControl Database"
4 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 |
6 | "github.com/RedHatGov/ocdb/actions"
7 | )
8 |
9 | // main is the starting point for your Buffalo application.
10 | // You can feel free and add to this `main` method, change
11 | // what it does, etc...
12 | // All we ask is that, at some point, you make sure to
13 | // call `app.Serve()`, unless you don't want to start your
14 | // application that is. :)
15 | func main() {
16 | app := actions.App()
17 | if err := app.Serve(); err != nil {
18 | log.Fatal(err)
19 | }
20 | }
21 |
22 | /*
23 | # Notes about `main.go`
24 |
25 | ## SSL Support
26 |
27 | We recommend placing your application behind a proxy, such as
28 | Apache or Nginx and letting them do the SSL heavy lifting
29 | for you. https://gobuffalo.io/en/docs/proxy
30 |
31 | ## Buffalo Build
32 |
33 | When `buffalo build` is run to compile your binary, this `main`
34 | function will be at the heart of that binary. It is expected
35 | that your `main` function will start your application using
36 | the `app.Serve()` method.
37 |
38 | */
39 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "buffalo",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "license": "MIT",
6 | "scripts": {
7 | "dev": "webpack --watch",
8 | "build": "webpack -p --progress"
9 | },
10 | "repository": "github.com/gobuffalo/buffalo",
11 | "dependencies": {
12 | "@babel/preset-react": "^7.10.4",
13 | "@mdx-js/loader": "^1.6.16",
14 | "@mdx-js/runtime": "^1.6.16",
15 | "@patternfly/react-charts": "^6.9.6",
16 | "@patternfly/react-core": "^4.47.0",
17 | "@patternfly/react-table": "^4.16.7",
18 | "@types/jest": "^26.0.13",
19 | "@types/react": "^16.9.49",
20 | "@types/react-dom": "^16.9.8",
21 | "@types/react-router-dom": "^5.1.5",
22 | "bootstrap": "4.5.2",
23 | "enzyme": "^3.10.0",
24 | "jquery": "~3.5.1",
25 | "jquery-ujs": "~1.2.2",
26 | "popper.js": "^1.14.4",
27 | "prop-types": "^15.7.2",
28 | "react": "^16.13.1",
29 | "react-axe": "^3.5.3",
30 | "react-document-title": "^2.0.3",
31 | "react-dom": "^16.13.1",
32 | "react-router-dom": "^5.2.0",
33 | "react-router-last-location": "^2.0.1",
34 | "ts-loader": "^8.0.3",
35 | "tsconfig-paths-webpack-plugin": "^3.3.0",
36 | "typescript": "^4.0.2",
37 | "yarn": "^1.22.5"
38 | },
39 | "devDependencies": {
40 | "@babel/cli": "^7.11.6",
41 | "@babel/core": "^7.11.6",
42 | "@babel/preset-env": "^7.11.5",
43 | "babel-loader": "^8.1.0",
44 | "copy-webpack-plugin": "~6.1.0",
45 | "css-loader": "~4.2.2",
46 | "expose-loader": "~1.0.0",
47 | "file-loader": "~6.1.0",
48 | "mini-css-extract-plugin": "^0.11.0",
49 | "node-sass": "~4.14.1",
50 | "sass-loader": "~10.0.2",
51 | "style-loader": "~1.2.1",
52 | "uglifyjs-webpack-plugin": "~2.2.0",
53 | "url-loader": "~4.1.0",
54 | "webpack": "~4.44.1",
55 | "webpack-clean-obsolete-chunks": "^0.4.0",
56 | "webpack-cli": "3.3.12",
57 | "webpack-livereload-plugin": "2.3.0",
58 | "webpack-manifest-plugin": "~2.2.0"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/backend/job/job.go:
--------------------------------------------------------------------------------
1 | package job
2 |
3 | import (
4 | "github.com/RedHatGov/ocdb/pkg/utils"
5 | "github.com/gobuffalo/buffalo/worker"
6 | "time"
7 | )
8 |
9 | type JobFn func() error
10 |
11 | type Job struct {
12 | Name string
13 | Fn JobFn `json:"-"`
14 | Period time.Duration
15 | DelayedStart time.Duration
16 | LastStart time.Time
17 | LastSuccess time.Time
18 | LastError string
19 | ErrorCount uint
20 | }
21 |
22 | func (job *Job) SetUpIn(w worker.Worker) {
23 | err := w.Register(job.Name, func(args worker.Args) error {
24 | job.reschedule(w, job.Period)
25 | return job.run()
26 | })
27 | if err != nil {
28 | utils.Log.Fatalf("Could not register job: %v", err)
29 | }
30 | job.reschedule(w, job.DelayedStart+time.Second)
31 | }
32 |
33 | func (job *Job) reschedule(w worker.Worker, period time.Duration) {
34 | err := w.PerformIn(worker.Job{
35 | Queue: "masonry",
36 | Handler: job.Name,
37 | }, period)
38 | if err != nil {
39 | utils.Log.Errorf("Could not reschedule job %s: %v", job.Name, err)
40 | }
41 | }
42 |
43 | func (job *Job) run() error {
44 | job.LastStart = time.Now()
45 |
46 | err := job.Fn()
47 | if err != nil {
48 | job.LastError = err.Error()
49 | job.ErrorCount += 1
50 | if job.ErrorCount < 16 {
51 | utils.Log.Errorf("Job '%s' Failed: %s. Re-trying", job.Name, job.LastError)
52 | return job.run()
53 | }
54 | } else {
55 | job.LastError = ""
56 | job.LastSuccess = time.Now()
57 | job.ErrorCount = 0
58 | }
59 | return err
60 | }
61 |
--------------------------------------------------------------------------------
/pkg/cac/acquire.go:
--------------------------------------------------------------------------------
1 | package cac
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/RedHatGov/ocdb/pkg/git"
7 | "github.com/RedHatGov/ocdb/pkg/utils"
8 |
9 | "os/exec"
10 | "sync"
11 | )
12 |
13 | var mux sync.Mutex
14 |
15 | const (
16 | gitCache = "/var/tmp/ocdb/ComplianceAsCode.content"
17 | buildCache = gitCache + "/build/"
18 | installCache = "/var/tmp/ocdb/ComplianceAsCode.content_install/"
19 | installScapCache = installCache + "share/xml/scap/ssg/content/"
20 | )
21 |
22 | // Refresh function refreshes masonry data
23 | func Refresh() error {
24 | mux.Lock()
25 | defer mux.Unlock()
26 | err := git.PullOrClone(gitCache, "https://github.com/ComplianceAsCode/content", nil)
27 | if err != nil {
28 | return err
29 | }
30 | err = cmake()
31 | if err != nil {
32 | return err
33 | }
34 | err = make()
35 | if err != nil {
36 | return err
37 | }
38 | err = buildRolieFeed()
39 | if err != nil {
40 | return err
41 | }
42 | return makeSrgCsv()
43 | }
44 |
45 | func make() error {
46 | makeCmd := exec.Command("make", "install")
47 | makeCmd.Dir = buildCache
48 | logWriter := utils.LogWriter{}
49 | makeCmd.Stdout = logWriter
50 | makeCmd.Stderr = logWriter
51 |
52 | err := makeCmd.Run()
53 | if err != nil {
54 | return fmt.Errorf("Error running make: %v", err)
55 | }
56 | return nil
57 | }
58 |
59 | func cmake() error {
60 | cmakeParams := []string{
61 | "-DSSG_PRODUCT_DEFAULT:BOOLEAN=FALSE",
62 | "-DSSG_PRODUCT_RHEL7:BOOLEAN=TRUE",
63 | "-DSSG_PRODUCT_RHEL8:BOOLEAN=TRUE",
64 | "-DSSG_PRODUCT_OCP4:BOOLEAN=TRUE",
65 | "-DSSG_PRODUCT_RHOSP13:BOOLEAN=TRUE",
66 | "-DSSG_PRODUCT_RHCOS4:BOOLEAN=TRUE",
67 | "-DSSG_PRODUCT_FIREFOX:BOOLEAN=TRUE",
68 | "-DCMAKE_BUILD_TYPE=Debug",
69 | "-DSSG_CENTOS_DERIVATIVES_ENABLED:BOOL=OFF",
70 | "-DSSG_SCIENTIFIC_LINUX_DERIVATIVES_ENABLED:BOOL=OFF",
71 | "-DSSG_OVAL_SCHEMATRON_VALIDATION_ENABLED=FALSE",
72 | "-DCMAKE_INSTALL_PREFIX=" + installCache,
73 | "../",
74 | }
75 | cmakeCmd := exec.Command("cmake", cmakeParams...)
76 | cmakeCmd.Dir = buildCache
77 | cmakeCmdErr := &bytes.Buffer{}
78 | cmakeCmd.Stdout = &utils.LogWriter{}
79 | cmakeCmd.Stderr = cmakeCmdErr
80 |
81 | err := cmakeCmd.Run()
82 | if err != nil || cmakeCmdErr.Len() > 0 {
83 | return fmt.Errorf("Error running cmake: %v, stderr was: %s", err, cmakeCmdErr.String())
84 | }
85 | return nil
86 | }
87 |
--------------------------------------------------------------------------------
/pkg/cac/csv.go:
--------------------------------------------------------------------------------
1 | package cac
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/RedHatGov/ocdb/pkg/static"
7 | "github.com/RedHatGov/ocdb/pkg/utils"
8 | "io/ioutil"
9 |
10 | "os/exec"
11 | )
12 |
13 | const xsltFilename = "srg-to-csv.xslt"
14 |
15 | func makeSrgCsv() error {
16 | err := unbundleXslt()
17 | if err != nil {
18 | return err
19 | }
20 | err = srgToCsv("rhel8")
21 | if err != nil {
22 | return err
23 | }
24 | return srgToCsv("rhel7")
25 | }
26 |
27 | func srgToCsv(product string) error {
28 | cmd := exec.Command("/usr/bin/xsltproc",
29 | "--stringparam", "map-to-items", product+"/xccdf-linked-srg-overlay.xml",
30 | "--stringparam", "ocil-document", product+"/ocil-linked.xml",
31 | "--output", "tables/table-"+product+"-srgmap-flat.csv",
32 | xsltFilename,
33 | "../shared/references/disa-os-srg-v1r6.xml")
34 | cmd.Dir = buildCache
35 | cmdErr := &bytes.Buffer{}
36 | cmd.Stdout = &utils.LogWriter{}
37 | cmd.Stderr = cmdErr
38 |
39 | err := cmd.Run()
40 | if err != nil || cmdErr.Len() > 0 {
41 | return fmt.Errorf("Error running xsltproc: %v, stderr was: %s", err, cmdErr.String())
42 | }
43 | return nil
44 | }
45 |
46 | func unbundleXslt() error {
47 | xsltBytes, err := static.AssetsBox.Find("/" + xsltFilename)
48 | if err != nil {
49 | return fmt.Errorf("Assets pack does not contain %s: %v", xsltFilename, err)
50 | }
51 | return ioutil.WriteFile(buildCache+xsltFilename, xsltBytes, 0600)
52 | }
53 |
--------------------------------------------------------------------------------
/pkg/cac/http.go:
--------------------------------------------------------------------------------
1 | package cac
2 |
3 | import (
4 | "net/http"
5 | )
6 |
7 | func BuildFiles() http.FileSystem {
8 | return http.Dir(buildCache)
9 | }
10 |
11 | func InstalledScapFiles() http.FileSystem {
12 | return http.Dir(installScapCache)
13 | }
14 |
--------------------------------------------------------------------------------
/pkg/cac/rolie.go:
--------------------------------------------------------------------------------
1 | package cac
2 |
3 | import (
4 | "github.com/RedHatGov/ocdb/pkg/utils"
5 | "github.com/rolieup/golie/pkg/rolie"
6 | )
7 |
8 | func buildRolieFeed() error {
9 | utils.Log.Debug("Re-building ROLIE feed.")
10 | builder := rolie.Builder{
11 | ID: "compliance-as-code",
12 | Title: "Rolie feed for the latest SCAP files by ComplianceAsCode",
13 | RootURI: "https://atopathways.redhatgov.io/compliance-as-code/scap/",
14 | DirectoryPath: installScapCache,
15 | }
16 | return builder.New()
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/pkg/cac_oscal/acquire.go:
--------------------------------------------------------------------------------
1 | package cac_oscal
2 |
3 | import (
4 | "fmt"
5 | "os/exec"
6 | "sync"
7 |
8 | "github.com/RedHatGov/ocdb/pkg/git"
9 | "github.com/RedHatGov/ocdb/pkg/utils"
10 | )
11 |
12 | var mux sync.Mutex
13 |
14 | const (
15 | gitCache = "/var/tmp/ocdb/ComplianceAsCode.oscal"
16 | docxCache = gitCache + "/docx"
17 | )
18 |
19 | // Refresh function refreshes masonry data
20 | func Refresh() error {
21 | mux.Lock()
22 | defer mux.Unlock()
23 | err := git.PullOrClone(gitCache, "https://github.com/ComplianceAsCode/oscal", nil)
24 | if err != nil {
25 | return err
26 | }
27 | return make("docx")
28 | }
29 |
30 | func make(target string) error {
31 | makeCmd := exec.Command("make", target)
32 | makeCmd.Dir = gitCache
33 | logWriter := utils.LogWriter{}
34 | makeCmd.Stdout = logWriter
35 | makeCmd.Stderr = logWriter
36 | err := makeCmd.Run()
37 | if err != nil {
38 | return fmt.Errorf("Error running make: %v", err)
39 | }
40 | return nil
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/cac_oscal/fedramp.go:
--------------------------------------------------------------------------------
1 | package cac_oscal
2 |
3 | import (
4 | "fmt"
5 | "io"
6 | "os"
7 | )
8 |
9 | func FedrampDocument(componentId, level, format string) (io.ReadCloser, error) {
10 | path := fedrampPath(componentId, level, format)
11 | switch format {
12 | case "docx":
13 | file, err := os.Open(path)
14 | if err != nil {
15 | err = buildFedrampDocx(componentId, level)
16 | if err != nil {
17 | return nil, err
18 | }
19 | file, err = os.Open(path)
20 | }
21 | return file, err
22 | case "xml":
23 | return os.Open(path)
24 | case "json":
25 | return os.Open(path)
26 | default:
27 | return nil, fmt.Errorf("Unsupported FedRAMP formatting: %s", format)
28 | }
29 | }
30 |
31 | func buildFedrampDocx(componentId, level string) error {
32 | return make("docx/" + fedrampFilename(componentId, level, "docx"))
33 | }
34 |
35 | func fedrampPath(componentId, level, format string) string {
36 | return fmt.Sprintf("%s/%s/%s", gitCache, format, fedrampFilename(componentId, level, format))
37 | }
38 |
39 | func fedrampFilename(componentId, level, format string) string {
40 | return fmt.Sprintf("%s-fedramp-%s.%s", componentId, level, format)
41 | }
42 |
--------------------------------------------------------------------------------
/pkg/git/easy.go:
--------------------------------------------------------------------------------
1 | package git
2 |
3 | import (
4 | "bytes"
5 | "fmt"
6 | "github.com/RedHatGov/ocdb/pkg/utils"
7 | "os"
8 | "os/exec"
9 | "strings"
10 | "time"
11 | )
12 |
13 | func Clone(gitRepo, directory string, since *time.Time) error {
14 | gitCmd := exec.Command("git", "clone", "--depth", "1", gitRepo, directory)
15 | if since != nil {
16 | gitCmd = exec.Command("git", "clone", "--shallow-since", since.AddDate(0, 0, -1).String(), gitRepo, directory)
17 | }
18 | logWriter := utils.LogWriter{}
19 | gitCmd.Stdout = &logWriter
20 | gitCmd.Stderr = &logWriter
21 |
22 | err := gitCmd.Run()
23 | if err != nil {
24 | return fmt.Errorf("Error running git clone %s: %v", gitRepo, err)
25 | }
26 | return nil
27 | }
28 |
29 | func Pull(directory string) error {
30 | gitCmd := exec.Command("git", "pull")
31 | gitCmd.Dir = directory
32 | logWriter := utils.LogWriter{}
33 | gitCmd.Stdout = &logWriter
34 | gitCmd.Stderr = &logWriter
35 |
36 | err := gitCmd.Run()
37 | if err != nil {
38 | return fmt.Errorf("Error running git pull in %s: %v", directory, err)
39 | }
40 | return nil
41 | }
42 |
43 | func PullOrClone(directory, gitRepo string, since *time.Time) error {
44 | if stat, err := os.Stat(directory); err == nil && stat.IsDir() {
45 | return Pull(directory)
46 | }
47 | return Clone(gitRepo, directory, since)
48 | }
49 |
50 | func LastCommitBy(directory string, date time.Time) (string, error) {
51 | command := fmt.Sprintf("git log --pretty=format:\"%%H\" --until=\"%s\" | head -n 1", date)
52 |
53 | gitCmd := exec.Command("bash", "-c", command)
54 | gitCmd.Dir = directory
55 | stdout := bytes.Buffer{}
56 | stderr := bytes.Buffer{}
57 | gitCmd.Stdout = &stdout
58 | gitCmd.Stderr = &stderr
59 |
60 | err := gitCmd.Run()
61 | if err != nil || stderr.Len() > 0 {
62 | return "", fmt.Errorf("Error running git log: %v; stderr: %s", err, stderr.String())
63 | }
64 |
65 | return strings.Replace(stdout.String(), "\n", "", 1), nil
66 | }
67 |
--------------------------------------------------------------------------------
/pkg/masonry/acquire.go:
--------------------------------------------------------------------------------
1 | package masonry
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/opencontrol/compliance-masonry/pkg/cli/get/resources"
7 | "github.com/opencontrol/compliance-masonry/pkg/lib"
8 | "github.com/opencontrol/compliance-masonry/pkg/lib/common"
9 | "github.com/opencontrol/compliance-masonry/pkg/lib/opencontrol"
10 | "github.com/opencontrol/compliance-masonry/pkg/lib/opencontrol/versions/1.0.0"
11 | )
12 |
13 | func (d *OpencontrolData) populateCacheDir(revision string) error {
14 | repo := make([]common.RemoteSource, 1)
15 | repo[0] = schema.VCSEntry{
16 | URL: "https://github.com/ComplianceAsCode/redhat",
17 | Revision: revision,
18 | Path: ""}
19 | getter := resources.NewVCSAndLocalGetter(opencontrol.YAMLParser{})
20 | return getter.GetRemoteResources(d.cacheDir, "opencontrols", repo)
21 | }
22 |
23 | func NewOpencontrolData(revision, cacheDir string) (*OpencontrolData, error) {
24 | res := OpencontrolData{cacheDir: cacheDir}
25 | err := res.populateCacheDir(revision)
26 | if err != nil {
27 | return nil, err
28 | }
29 |
30 | var errs []error
31 | res.workspace, errs = lib.LoadData(res.cacheDir, res.certDir()+"dhs-4300a.yaml")
32 | if errs != nil {
33 | msg := "Error occurred during loading open control masonry data"
34 | for _, e := range errs {
35 | msg = fmt.Sprintf("%s: %v", msg, e)
36 | }
37 | return nil, errors.New(msg)
38 | }
39 | return &res, res.buildCache()
40 | }
41 |
--------------------------------------------------------------------------------
/pkg/masonry/certifications.go:
--------------------------------------------------------------------------------
1 | package masonry
2 |
3 | import (
4 | "fmt"
5 | "github.com/opencontrol/compliance-masonry/pkg/lib/certifications"
6 | "github.com/opencontrol/compliance-masonry/pkg/lib/common"
7 | "io/ioutil"
8 | )
9 |
10 | type StandardSubset map[string]bool
11 |
12 | type Certification struct {
13 | Key string
14 | Controls map[string]StandardSubset
15 | }
16 |
17 | func (d *OpencontrolData) GetAllCertifications() map[string]Certification {
18 | return d.certificationsCache
19 | }
20 |
21 | func (d *OpencontrolData) buildCache() error {
22 | d.certificationsCache = map[string]Certification{}
23 | fileinfos, err := ioutil.ReadDir(d.certDir())
24 | if err != nil {
25 | return err
26 | }
27 | for _, fileinfo := range fileinfos {
28 | cert, err := certifications.Load(d.certDir() + fileinfo.Name())
29 | if err != nil {
30 | return err
31 | }
32 | controls := map[string]StandardSubset{}
33 | for _, name := range cert.GetSortedStandards() {
34 | controls[name], err = d.filterKeysInStandard(cert, name)
35 | if err != nil {
36 | return err
37 | }
38 | }
39 |
40 | d.certificationsCache[cert.GetKey()] = Certification{
41 | Key: cert.GetKey(),
42 | Controls: controls,
43 | }
44 | }
45 | return nil
46 | }
47 |
48 | func (d *OpencontrolData) filterKeysInStandard(cert common.Certification, standardName string) (StandardSubset, error) {
49 | standard, found := d.workspace.GetStandard(standardName)
50 | if !found {
51 | return nil, fmt.Errorf("Did not found standard %s referenced by certification %s", standardName, cert.GetKey())
52 | }
53 | validControls := standard.GetControls()
54 | res := StandardSubset{}
55 | for _, ctrl := range cert.GetControlKeysFor(standardName) {
56 | _, found = validControls[ctrl]
57 | if found {
58 | res[ctrl] = true
59 | }
60 | }
61 | return res, nil
62 | }
63 |
64 | func (d *OpencontrolData) certDir() string {
65 | return d.cacheDir + "/certifications/"
66 | }
67 |
--------------------------------------------------------------------------------
/pkg/masonry/logical_view.go:
--------------------------------------------------------------------------------
1 | package masonry
2 |
3 | import (
4 | "fmt"
5 | "github.com/opencontrol/compliance-masonry/pkg/lib/common"
6 | )
7 |
8 | // CustomControl is object that ties together information from standard with product specific "satisfaction" description
9 | type CustomControl struct {
10 | Key string
11 | Control common.Control
12 | Satisfies common.Satisfies
13 | }
14 |
15 | func standardToLogicalView(s common.Standard) map[string][]CustomControl {
16 | result := make(map[string][]CustomControl)
17 | controls := s.GetControls()
18 | for _, controlName := range s.GetSortedControls() {
19 | control := controls[controlName]
20 | _, ok := result[control.GetFamily()]
21 | if !ok {
22 | result[control.GetFamily()] = make([]CustomControl, 0)
23 | }
24 | result[control.GetFamily()] = append(result[control.GetFamily()], CustomControl{
25 | Key: controlName,
26 | Control: control})
27 | }
28 | return result
29 | }
30 |
31 | var validImplementationStatuses = []string{"complete", "partial", "not applicable", "planned", "unsatisfied", "unknown", "none"}
32 |
33 | type ControlsByFamilies map[string]map[string][]CustomControl
34 |
35 | func (ms *OpencontrolData) ComponentLogicalView(c common.Component) map[string]interface{} {
36 | ctrls, problems := ms.controlsByFamilies(c)
37 | result := make(map[string]interface{})
38 | result["name"] = c.GetName()
39 | result["controls"] = ctrls
40 | result["errors"] = problems
41 | return result
42 | }
43 |
44 | func (ms *OpencontrolData) controlsByFamilies(c common.Component) (ControlsByFamilies, []string) {
45 | result := make(ControlsByFamilies)
46 | problems := make([]string, 0)
47 |
48 | for _, satisfy := range c.GetAllSatisfies() {
49 | standardKey := satisfy.GetStandardKey()
50 | _, ok := result[standardKey]
51 | if !ok {
52 | standard, found := ms.GetStandard(standardKey)
53 | if found {
54 | result[standardKey] = standardToLogicalView(standard)
55 | }
56 |
57 | }
58 | found := false
59 | for groupId, group := range result[standardKey] {
60 | for i, cc := range group {
61 | if cc.Key == satisfy.GetControlKey() {
62 | if cc.Satisfies != nil {
63 | problems = append(problems, fmt.Sprintf("Found duplicate item: %s", cc.Key))
64 | }
65 |
66 | result[standardKey][groupId][i].Satisfies = satisfy
67 | found = true
68 |
69 | switch satisfy.GetImplementationStatus() {
70 | case "complete", "partial", "not applicable", "planned", "unsatisfied", "unknown", "none":
71 | break
72 | default:
73 | problems = append(problems,
74 | fmt.Sprintf("Found non-standard implementation_status: %s. Expected one of %s.",
75 | satisfy.GetImplementationStatus(), validImplementationStatuses))
76 | break
77 | }
78 |
79 | break
80 | }
81 |
82 | }
83 | if found {
84 | break
85 | }
86 | }
87 | if !found {
88 | problems = append(problems, fmt.Sprintf("Could not find reference %s in the standard %s", satisfy.GetControlKey(), standardKey))
89 |
90 | }
91 | }
92 |
93 | return result, problems
94 | }
95 |
--------------------------------------------------------------------------------
/pkg/masonry/singleton.go:
--------------------------------------------------------------------------------
1 | package masonry
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | var instance *OpencontrolData
8 | var mux sync.Mutex
9 |
10 | // GetInstance gets memory representation of the masonry cache
11 | func GetInstance() *OpencontrolData {
12 | if instance == nil {
13 | mux.Lock()
14 | mux.Unlock()
15 | if instance == nil {
16 | Refresh()
17 | }
18 | }
19 | return instance
20 | }
21 |
22 | // Refresh function refreshes masonry data
23 | func Refresh() error {
24 | mux.Lock()
25 | defer mux.Unlock()
26 | data, err := NewOpencontrolData("master", "/var/tmp/ocdb/masonry_cache")
27 | if err != nil {
28 | return err
29 | }
30 | instance = data
31 | return nil
32 | }
33 |
--------------------------------------------------------------------------------
/pkg/masonry/stats/history.go:
--------------------------------------------------------------------------------
1 | package stats
2 |
3 | import (
4 | "github.com/RedHatGov/ocdb/pkg/git"
5 | "github.com/RedHatGov/ocdb/pkg/masonry"
6 | "github.com/opencontrol/compliance-masonry/pkg/lib/common"
7 | "sync"
8 | "time"
9 | )
10 |
11 | const (
12 | // how many months back we want to in the statistics?
13 | startingYear = 2020
14 | startingMonth = 01
15 | ocGit = "https://github.com/ComplianceAsCode/redhat"
16 | ocDir = "/var/tmp/ocdb/ComplianceAsCode.redhat"
17 | )
18 |
19 | type HistoricalStats map[string]ComponentStats
20 |
21 | type ComponentStats map[string]CertificationStats
22 |
23 | type CertificationStats struct {
24 | Certification string
25 | History []ResultSnapshot
26 | PerFamily map[string]ControlResponses
27 | }
28 |
29 | type ResultSnapshot struct {
30 | Time time.Time
31 | Stats ControlResponses
32 | }
33 |
34 | type ControlResponses map[string]int
35 |
36 | var hsInstance *HistoricalStats
37 | var hsMux sync.Mutex
38 |
39 | func getHistoricalStats() *HistoricalStats {
40 | if hsInstance == nil {
41 | RefreshHistoryStatistics()
42 | }
43 | return hsInstance
44 | }
45 |
46 | func GetHistoricalStats(componentID string) (ComponentStats, bool) {
47 | instance := getHistoricalStats()
48 | stats, found := (*instance)[componentID]
49 | return stats, found
50 | }
51 |
52 | func RefreshHistoryStatistics() error {
53 | hsMux.Lock()
54 | defer hsMux.Unlock()
55 |
56 | startDate := time.Date(startingYear, time.Month(startingMonth), 1, 0, 0, 0, 0, time.UTC)
57 | res, err := buildHistoricalStats(startDate)
58 | if err != nil {
59 | return err
60 | }
61 | hsInstance = &res
62 | return nil
63 | }
64 |
65 | func buildHistoricalStats(startDate time.Time) (HistoricalStats, error) {
66 | res := HistoricalStats{}
67 | gitStartDate := startDate.AddDate(0, -1, 0)
68 | err := git.PullOrClone(ocDir, ocGit, &(gitStartDate))
69 | if err != nil {
70 | return res, err
71 | }
72 |
73 | for date := range generateDatesBiMonthly(startDate) {
74 | oc, err := opencontrolsByDate(ocDir, date)
75 | if err != nil {
76 | return res, err
77 | }
78 |
79 | for _, component := range oc.GetAllComponents() {
80 | res.AddData(date, oc.GetAllCertifications(), component)
81 | }
82 | }
83 | return res, nil
84 | }
85 |
86 | func (stats HistoricalStats) AddData(date time.Time, certifications map[string]masonry.Certification, component common.Component) {
87 | _, found := stats[component.GetKey()]
88 | if !found {
89 | stats[component.GetKey()] = ComponentStats{}
90 | }
91 | stats[component.GetKey()].AddData(date, certifications, component)
92 | }
93 |
94 | func (stats ComponentStats) AddData(date time.Time, certifications map[string]masonry.Certification, component common.Component) {
95 | satisfiesMap := map[string]string{}
96 | for _, sat := range component.GetAllSatisfies() {
97 | satisfiesMap[sat.GetControlKey()] = sat.GetImplementationStatus()
98 | }
99 |
100 | for _, cert := range certifications {
101 | _, found := stats[cert.Key]
102 | if !found {
103 | stats[cert.Key] = CertificationStats{Certification: cert.Key, History: []ResultSnapshot{}}
104 | }
105 | cs := stats[cert.Key]
106 | cs.History = append(cs.History, resultSnapshot(date, cert, satisfiesMap))
107 | cs.buildPerFamilyStats(cert, satisfiesMap)
108 | stats[cert.Key] = cs
109 | }
110 | }
111 |
112 | func (stats *CertificationStats) buildPerFamilyStats(cert masonry.Certification, satisfiesMap map[string]string) {
113 | stats.PerFamily = map[string]ControlResponses{}
114 | for standardName, subSet := range cert.Controls {
115 | if standardName == "NIST-800-53" {
116 | for ctrlID := range subSet {
117 | family := ctrlID[0:2]
118 | _, found := stats.PerFamily[family]
119 | if !found {
120 | stats.PerFamily[family] = ControlResponses{}
121 | }
122 |
123 | stats.PerFamily[family].AddData(ctrlID, satisfiesMap)
124 | }
125 | }
126 | }
127 | }
128 |
129 | func resultSnapshot(date time.Time, cert masonry.Certification, satisfiesMap map[string]string) ResultSnapshot {
130 | res := ResultSnapshot{Time: date, Stats: ControlResponses{}}
131 | for standardName, subSet := range cert.Controls {
132 | if standardName == "NIST-800-53" {
133 | for ctrlID := range subSet {
134 | res.Stats.AddData(ctrlID, satisfiesMap)
135 | }
136 | }
137 | }
138 | return res
139 | }
140 |
141 | func (responses ControlResponses) AddData(ctrlID string, satisfiesMap map[string]string) {
142 | status, found := satisfiesMap[ctrlID]
143 | if !found {
144 | status = "unknown"
145 | }
146 | prev, found := responses[status]
147 | if !found {
148 | prev = 0
149 | }
150 | responses[status] = prev + 1
151 | }
152 |
153 | func opencontrolsByDate(ocDir string, date time.Time) (*masonry.OpencontrolData, error) {
154 | gitSha, err := git.LastCommitBy(ocDir, date)
155 | if err != nil {
156 | return nil, err
157 | }
158 | return masonry.NewOpencontrolData(gitSha, "/var/tmp/ocdb/ComplianceAsCode.redhat.rev")
159 | }
160 |
161 | func generateDatesBiMonthly(since time.Time) chan time.Time {
162 | today := time.Now()
163 | ch := make(chan time.Time)
164 | go func() {
165 | for date := since; today.After(date); date = date.AddDate(0, 2, 0) {
166 | ch <- date
167 | }
168 | ch <- today
169 | close(ch)
170 | }()
171 | return ch
172 | }
173 |
--------------------------------------------------------------------------------
/pkg/masonry/wrapper.go:
--------------------------------------------------------------------------------
1 | package masonry
2 |
3 | import (
4 | "github.com/opencontrol/compliance-masonry/pkg/lib/common"
5 | )
6 |
7 | type OpencontrolData struct {
8 | workspace common.Workspace
9 | cacheDir string
10 | certificationsCache map[string]Certification
11 | }
12 |
13 | func (d *OpencontrolData) GetAllComponents() []common.Component {
14 | return d.workspace.GetAllComponents()
15 | }
16 |
17 | func (d *OpencontrolData) GetComponent(id string) (common.Component, bool) {
18 | return d.workspace.GetComponent(id)
19 | }
20 |
21 | func (d *OpencontrolData) GetAllStandards() []common.Standard {
22 | return d.workspace.GetAllStandards()
23 | }
24 |
25 | func (d *OpencontrolData) GetStandard(id string) (common.Standard, bool) {
26 | return d.workspace.GetStandard(id)
27 | }
28 |
29 | func (d *OpencontrolData) PathToComponentYaml(componentId string) string {
30 | return d.cacheDir + "/components/" + componentId + "/component.yaml"
31 | }
32 |
--------------------------------------------------------------------------------
/pkg/static/assets.go:
--------------------------------------------------------------------------------
1 | package static
2 |
3 | import "github.com/gobuffalo/packr/v2"
4 |
5 | // Represents files under our the assets directory
6 | var AssetsBox = packr.New("app:assets", "../../public")
7 |
--------------------------------------------------------------------------------
/pkg/utils/log_writer.go:
--------------------------------------------------------------------------------
1 | package utils
2 |
3 | import "github.com/gobuffalo/buffalo"
4 |
5 | var Log buffalo.Logger
6 |
7 | func SetLogger(logger buffalo.Logger) {
8 | Log = logger
9 | }
10 |
11 | type LogWriter struct{}
12 |
13 | func (LogWriter) Write(p []byte) (n int, err error) {
14 | length := len(p)
15 | if p[length-1] == '\n' {
16 | p = p[:length-1]
17 | }
18 | Log.Debug(string(p))
19 | return length, nil
20 | }
21 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | User-agent: *
2 | Disallow: /
3 |
--------------------------------------------------------------------------------
/public/srg-to-csv.xslt:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | https://public.cyber.mil/stigs/downloads/?_dl_facet_stigs=operating-systems%2Cgeneral-purpose-os
13 |
14 |
15 |
16 |
17 | CCI
18 | SRGID
19 | STIGID
20 | SRG Requirement
21 | Requirement
22 | SRG VulDiscussion
23 | VulDiscussion
24 | Status
25 | SRG Check
26 | Check
27 | SRG Fix
28 | Fix
29 | Severity
30 | Mitigation
31 | Artifact Description
32 | Status Justification
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | :
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | Applicable - Inherently Meets
109 |
110 |
111 | N/A
112 |
113 |
114 | Applicable - Configurable
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
--------------------------------------------------------------------------------
/templates/_flash.plush.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%= for (k, messages) in flash { %>
4 | <%= for (msg) in messages { %>
5 |
6 | <%= msg %>
7 |
8 | ×
9 |
10 |
11 | <% } %>
12 | <% } %>
13 |
14 |
15 |
--------------------------------------------------------------------------------
/templates/application.plush.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | OpenControl Database
9 |
10 |
11 | ">
12 | ">
13 |
14 |
15 |
22 | <%= javascriptTag("app.js") %>
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/templates/index.plush.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/RedHatGov/ocdb/23d941ac48d1f789d309def511a1910d07fdf2ee/templates/index.plush.html
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "rootDir": ".",
5 | "outDir": "dist",
6 | "module": "esnext",
7 | "target": "es5",
8 | "lib": ["es6", "dom"],
9 | "sourceMap": true,
10 | "jsx": "react",
11 | "moduleResolution": "node",
12 | "forceConsistentCasingInFileNames": true,
13 | "noImplicitReturns": true,
14 | "noImplicitThis": true,
15 | "noImplicitAny": false,
16 | "allowJs": true,
17 | "esModuleInterop": true,
18 | "allowSyntheticDefaultImports": true,
19 | "strict": true,
20 | "resolveJsonModule": true,
21 | "paths": {
22 | "@app/*": ["assets/src/app/*"],
23 | "@assets/*": ["node_modules/@patternfly/react-core/dist/styles/assets/*"]
24 | },
25 | },
26 | "include": [
27 | "**/*.ts",
28 | "**/*.tsx"
29 | ],
30 | "exclude": ["node_modules"]
31 | }
32 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const Webpack = require("webpack");
2 | const Glob = require("glob");
3 | const path = require('path');
4 | const CopyWebpackPlugin = require("copy-webpack-plugin");
5 | const MiniCssExtractPlugin = require("mini-css-extract-plugin");
6 | const ManifestPlugin = require("webpack-manifest-plugin");
7 | const CleanObsoleteChunks = require('webpack-clean-obsolete-chunks');
8 | const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
9 | const LiveReloadPlugin = require('webpack-livereload-plugin');
10 | const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
11 |
12 | const configurator = {
13 | plugins() {
14 | var plugins = [
15 | new CleanObsoleteChunks(),
16 | new Webpack.ProvidePlugin({$: "jquery",jQuery: "jquery"}),
17 | new MiniCssExtractPlugin({filename: "[name].[contenthash].css"}),
18 | new CopyWebpackPlugin({patterns: [{from: "./assets",to: ""}]}, {copyUnmodified: true,ignore: ["css/**", "js/**", "src/**"] }),
19 | new Webpack.LoaderOptionsPlugin({minimize: true,debug: false}),
20 | new ManifestPlugin({fileName: "manifest.json"})
21 | ];
22 |
23 | return plugins
24 | },
25 |
26 | moduleOptions: function() {
27 | return {
28 | rules: [
29 | {
30 | test: /\.s[ac]ss$/,
31 | use: [
32 | MiniCssExtractPlugin.loader,
33 | { loader: "css-loader", options: {sourceMap: true}},
34 | { loader: "sass-loader", options: {sourceMap: true, includePaths: [ "/node_modules/@patternfly/patternfly/" ]}}
35 | ]
36 | },
37 | { test: /\.tsx?$/, use: "ts-loader", exclude: /node_modules/},
38 | { test: /\.jsx?$/, loader: "babel-loader",exclude: /node_modules/ },
39 | { test: /\.(woff|woff2|ttf|svg|png|jpg)(\?v=\d+\.\d+\.\d+)?$/,use: "url-loader"},
40 | { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,use: "file-loader" },
41 | { test: require.resolve("jquery"),use: "expose-loader?jQuery!expose-loader?$"},
42 | { test: /\.css$/, use: ["style-loader", "css-loader"] },
43 | { test: /\.mdx?$/, use: ['babel-loader', '@mdx-js/loader']},
44 | ]
45 | }
46 | },
47 |
48 | buildConfig: function(){
49 | // NOTE: If you are having issues with this not being set "properly", make
50 | // sure your GO_ENV is set properly as `buffalo build` overrides NODE_ENV
51 | // with whatever GO_ENV is set to or "development".
52 | const env = process.env.NODE_ENV || "development";
53 |
54 | var config = {
55 | mode: env,
56 | entry: { app: path.resolve(__dirname, 'assets', 'src', 'index.tsx') },
57 | output: {filename: "[name].[hash].js", path: `${__dirname}/public/assets`},
58 | plugins: configurator.plugins(),
59 | module: configurator.moduleOptions(),
60 | resolve: {
61 | extensions: ['.ts', '.js', '.json', '.tsx', '.md'],
62 | plugins: [
63 | new TsconfigPathsPlugin({
64 | configFile: path.resolve(__dirname, './tsconfig.json')
65 | })
66 | ]
67 | },
68 | node: { fs: 'empty' },
69 | }
70 |
71 | if( env === "development" ){
72 | config.plugins.push(new LiveReloadPlugin({appendScriptTag: true}))
73 | return config
74 | }
75 |
76 | const uglifier = new UglifyJsPlugin({
77 | uglifyOptions: {
78 | beautify: false,
79 | mangle: {keep_fnames: true},
80 | output: {comments: false},
81 | compress: {}
82 | }
83 | })
84 |
85 | config.optimization = {
86 | minimizer: [uglifier]
87 | }
88 |
89 | return config
90 | }
91 | }
92 |
93 | module.exports = configurator.buildConfig()
94 |
--------------------------------------------------------------------------------