forked from p15670423/monkey
Configuration submission
This commit is contained in:
parent
b319bc8e5f
commit
88229e74c9
|
@ -29,7 +29,7 @@ from cc.resources.telemetry import Telemetry
|
||||||
from cc.resources.telemetry_feed import TelemetryFeed
|
from cc.resources.telemetry_feed import TelemetryFeed
|
||||||
from cc.resources.pba_file_download import PBAFileDownload
|
from cc.resources.pba_file_download import PBAFileDownload
|
||||||
from cc.resources.pba_file_upload import FileUpload
|
from cc.resources.pba_file_upload import FileUpload
|
||||||
from cc.resources.attck import AttckConfiguration
|
from cc.resources.attack import AttckConfiguration
|
||||||
from cc.services.config import ConfigService
|
from cc.services.config import ConfigService
|
||||||
|
|
||||||
__author__ = 'Barak'
|
__author__ = 'Barak'
|
||||||
|
@ -124,6 +124,6 @@ def init_app(mongo_url):
|
||||||
'/api/fileUpload/<string:file_type>?load=<string:filename>',
|
'/api/fileUpload/<string:file_type>?load=<string:filename>',
|
||||||
'/api/fileUpload/<string:file_type>?restore=<string:filename>')
|
'/api/fileUpload/<string:file_type>?restore=<string:filename>')
|
||||||
api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/')
|
api.add_resource(RemoteRun, '/api/remote-monkey', '/api/remote-monkey/')
|
||||||
api.add_resource(AttckConfiguration, '/api/attck')
|
api.add_resource(AttckConfiguration, '/api/attack')
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import flask_restful
|
||||||
|
import json
|
||||||
|
from flask import jsonify, request
|
||||||
|
|
||||||
|
from cc.auth import jwt_required
|
||||||
|
from cc.services.attack.attack import AttackService
|
||||||
|
|
||||||
|
|
||||||
|
class AttckConfiguration(flask_restful.Resource):
|
||||||
|
@jwt_required()
|
||||||
|
def get(self):
|
||||||
|
return jsonify(configuration=AttackService.get_config()['properties'])
|
||||||
|
|
||||||
|
@jwt_required()
|
||||||
|
def post(self):
|
||||||
|
AttackService.update_config({'properties': json.loads(request.data)})
|
||||||
|
return {}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
import flask_restful
|
|
||||||
from flask import jsonify
|
|
||||||
|
|
||||||
from cc.auth import jwt_required
|
|
||||||
from cc.services.attck.attck import AttckService
|
|
||||||
|
|
||||||
|
|
||||||
class AttckConfiguration(flask_restful.Resource):
|
|
||||||
@jwt_required()
|
|
||||||
def get(self):
|
|
||||||
return jsonify(configuration=AttckService.get_config()['properties'])
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ from flask import request, make_response, jsonify
|
||||||
from cc.auth import jwt_required
|
from cc.auth import jwt_required
|
||||||
from cc.database import mongo
|
from cc.database import mongo
|
||||||
from cc.services.config import ConfigService
|
from cc.services.config import ConfigService
|
||||||
|
from cc.services.attack.attack import AttackService
|
||||||
from cc.services.node import NodeService
|
from cc.services.node import NodeService
|
||||||
from cc.services.report import ReportService
|
from cc.services.report import ReportService
|
||||||
from cc.utils import local_ip_addresses
|
from cc.utils import local_ip_addresses
|
||||||
|
@ -47,6 +48,7 @@ class Root(flask_restful.Resource):
|
||||||
# We can't drop system collections.
|
# We can't drop system collections.
|
||||||
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
|
[mongo.db[x].drop() for x in mongo.db.collection_names() if not x.startswith('system.')]
|
||||||
ConfigService.init_config()
|
ConfigService.init_config()
|
||||||
|
AttackService.reset_config()
|
||||||
logger.info('DB was reset')
|
logger.info('DB was reset')
|
||||||
return jsonify(status='OK')
|
return jsonify(status='OK')
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
import logging
|
||||||
|
from cc.database import mongo
|
||||||
|
from attack_schema import SCHEMA
|
||||||
|
|
||||||
|
__author__ = "VakarisZ"
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AttackService:
|
||||||
|
default_config = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_config():
|
||||||
|
config = mongo.db.attack.find_one({'name': 'newconfig'}) or AttackService.get_default_config()
|
||||||
|
return config
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_config_schema():
|
||||||
|
return SCHEMA
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def reset_config():
|
||||||
|
config = AttackService.get_default_config()
|
||||||
|
AttackService.update_config(config)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_config(config_json):
|
||||||
|
mongo.db.attack.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def parse_users_matrix(data):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_default_config():
|
||||||
|
if not AttackService.default_config:
|
||||||
|
AttackService.update_config(SCHEMA)
|
||||||
|
AttackService.default_config = SCHEMA
|
||||||
|
return AttackService.default_config
|
|
@ -9,7 +9,8 @@ SCHEMA = {
|
||||||
"T1210": {
|
"T1210": {
|
||||||
"title": "T1210 Exploitation of Remote services",
|
"title": "T1210 Exploitation of Remote services",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default": True,
|
"value": True,
|
||||||
|
"necessary": False,
|
||||||
"description": "Exploitation of a software vulnerability occurs when an adversary "
|
"description": "Exploitation of a software vulnerability occurs when an adversary "
|
||||||
"takes advantage of a programming error in a program, service, or within the "
|
"takes advantage of a programming error in a program, service, or within the "
|
||||||
"operating system software or kernel itself to execute adversary-controlled code."
|
"operating system software or kernel itself to execute adversary-controlled code."
|
||||||
|
@ -17,7 +18,8 @@ SCHEMA = {
|
||||||
"T1075": {
|
"T1075": {
|
||||||
"title": "T1075 Pass the hash",
|
"title": "T1075 Pass the hash",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default": True,
|
"value": True,
|
||||||
|
"necessary": False,
|
||||||
"description": "Pass the hash (PtH) is a method of authenticating as a user without "
|
"description": "Pass the hash (PtH) is a method of authenticating as a user without "
|
||||||
"having access to the user's cleartext password."
|
"having access to the user's cleartext password."
|
||||||
}
|
}
|
||||||
|
@ -30,7 +32,8 @@ SCHEMA = {
|
||||||
"T1110": {
|
"T1110": {
|
||||||
"title": "T1110 Brute force",
|
"title": "T1110 Brute force",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default": True,
|
"value": False,
|
||||||
|
"necessary": False,
|
||||||
"description": "Adversaries may use brute force techniques to attempt access to accounts "
|
"description": "Adversaries may use brute force techniques to attempt access to accounts "
|
||||||
"when passwords are unknown or when password hashes are obtained."
|
"when passwords are unknown or when password hashes are obtained."
|
||||||
}
|
}
|
||||||
|
@ -43,7 +46,8 @@ SCHEMA = {
|
||||||
"T1197": {
|
"T1197": {
|
||||||
"title": "T1197 Bits jobs",
|
"title": "T1197 Bits jobs",
|
||||||
"type": "bool",
|
"type": "bool",
|
||||||
"default": True,
|
"value": True,
|
||||||
|
"necessary": True,
|
||||||
"description": "Adversaries may abuse BITS to download, execute, "
|
"description": "Adversaries may abuse BITS to download, execute, "
|
||||||
"and even clean up after running malicious code."
|
"and even clean up after running malicious code."
|
||||||
}
|
}
|
|
@ -1,43 +0,0 @@
|
||||||
import logging
|
|
||||||
from cc.database import mongo
|
|
||||||
from attck_schema import SCHEMA
|
|
||||||
from jsonschema import Draft4Validator, validators
|
|
||||||
|
|
||||||
__author__ = "VakarisZ"
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class AttckService:
|
|
||||||
default_config = None
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_config():
|
|
||||||
config = mongo.db.attck.find_one({'name': 'newconfig'}) or AttckService.get_default_config()
|
|
||||||
return config
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_config_schema():
|
|
||||||
return SCHEMA
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def reset_config():
|
|
||||||
config = AttckService.get_default_config()
|
|
||||||
AttckService.update_config(config)
|
|
||||||
logger.info('Monkey config reset was called')
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def update_config(config_json):
|
|
||||||
mongo.db.attck.update({'name': 'newconfig'}, {"$set": config_json}, upsert=True)
|
|
||||||
logger.info('Attck config was updated')
|
|
||||||
return True
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_default_config():
|
|
||||||
if not AttckService.default_config:
|
|
||||||
AttckService.update_config(SCHEMA)
|
|
||||||
AttckService.default_config = SCHEMA
|
|
||||||
return AttckService.default_config
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
import React from 'react';
|
||||||
|
import Checkbox from '../ui-components/checkbox'
|
||||||
|
import Tooltip from 'react-tooltip-lite'
|
||||||
|
import AuthComponent from '../AuthComponent';
|
||||||
|
import ReactTable from "react-table";
|
||||||
|
import 'filepond/dist/filepond.min.css';
|
||||||
|
import '../../styles/Tooltip.scss';
|
||||||
|
|
||||||
|
|
||||||
|
// Finds which attack type has most techniques and returns that number
|
||||||
|
let findMaxTechniques = function (data){
|
||||||
|
let maxLen = 0;
|
||||||
|
data.forEach(function(techType) {
|
||||||
|
if (Object.keys(techType.properties).length > maxLen){
|
||||||
|
maxLen = Object.keys(techType.properties).length
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return maxLen
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parses config schema into data suitable for react-table (ATT&CK matrix)
|
||||||
|
let parseTechniques = function (data, maxLen) {
|
||||||
|
let techniques = [];
|
||||||
|
// Create rows with attack techniques
|
||||||
|
for (let i = 0; i < maxLen; i++) {
|
||||||
|
let row = {};
|
||||||
|
data.forEach(function(techType){
|
||||||
|
let rowColumn = {};
|
||||||
|
rowColumn.techName = techType.title;
|
||||||
|
|
||||||
|
if (i <= Object.keys(techType.properties).length) {
|
||||||
|
rowColumn.technique = Object.values(techType.properties)[i];
|
||||||
|
if (rowColumn.technique){
|
||||||
|
rowColumn.technique.name = Object.keys(techType.properties)[i]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rowColumn.technique = null
|
||||||
|
}
|
||||||
|
row[rowColumn.techName] = rowColumn
|
||||||
|
});
|
||||||
|
techniques.push(row)
|
||||||
|
}
|
||||||
|
return techniques;
|
||||||
|
};
|
||||||
|
|
||||||
|
class MatrixComponent extends AuthComponent {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {lastAction: 'none', matrixData: this.props.configuration};
|
||||||
|
// Copy configuration and parse it for ATT&CK matrix table
|
||||||
|
let configCopy = JSON.parse(JSON.stringify(this.props.configuration));
|
||||||
|
this.maxTechniques = findMaxTechniques(Object.values(configCopy));
|
||||||
|
this.data = parseTechniques(Object.values(configCopy), this.maxTechniques);
|
||||||
|
}
|
||||||
|
|
||||||
|
getColumns() {
|
||||||
|
return Object.keys(this.data[0]).map((key)=>{
|
||||||
|
return {
|
||||||
|
Header: key,
|
||||||
|
id: key,
|
||||||
|
accessor: x => this.renderTechnique(x[key].technique),
|
||||||
|
style: { 'whiteSpace': 'unset' }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTechnique(technique) {
|
||||||
|
if (technique == null){
|
||||||
|
return (<div></div>)
|
||||||
|
} else {
|
||||||
|
return (<Tooltip content={technique.description} direction="down">
|
||||||
|
<Checkbox checked={technique.value}
|
||||||
|
necessary={technique.necessary}
|
||||||
|
name={technique.name}
|
||||||
|
changeHandler={this.handleTechniqueChange}>
|
||||||
|
{technique.title}
|
||||||
|
</Checkbox>
|
||||||
|
</Tooltip>)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onSubmit = () => {
|
||||||
|
console.log(this.state.matrixData);
|
||||||
|
this.authFetch('/api/attack',
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify(this.state.matrixData)
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
if (!res.ok)
|
||||||
|
{
|
||||||
|
throw Error()
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}).then(
|
||||||
|
this.setState({
|
||||||
|
lastAction: 'saved'
|
||||||
|
})
|
||||||
|
).catch(error => {
|
||||||
|
console.log('bad configuration');
|
||||||
|
this.setState({lastAction: 'invalid_configuration'});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleTechniqueChange = (technique, value) => {
|
||||||
|
Object.entries(this.state.matrixData).forEach(techType => {
|
||||||
|
if(techType[1].properties.hasOwnProperty(technique)){
|
||||||
|
let tempMatrix = this.state.matrixData;
|
||||||
|
tempMatrix[techType[0]].properties[technique].value = value;
|
||||||
|
this.setState({matrixData: tempMatrix});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let columns = this.getColumns();
|
||||||
|
return (
|
||||||
|
<div className={"attack-matrix"}>
|
||||||
|
<form onSubmit={this.onSubmit}>
|
||||||
|
<ReactTable
|
||||||
|
columns={columns}
|
||||||
|
data={this.data}
|
||||||
|
showPagination={false}
|
||||||
|
defaultPageSize={this.maxTechniques} />
|
||||||
|
<div className={"messages"}>
|
||||||
|
{ this.state.lastAction === 'reset' ?
|
||||||
|
<div className="alert alert-success">
|
||||||
|
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
|
||||||
|
Matrix reset to default.
|
||||||
|
</div>
|
||||||
|
: ''}
|
||||||
|
{ this.state.lastAction === 'saved' ?
|
||||||
|
<div className="alert alert-success">
|
||||||
|
<i className="glyphicon glyphicon-ok-sign" style={{'marginRight': '5px'}}/>
|
||||||
|
Matrix applied to configuration.
|
||||||
|
</div>
|
||||||
|
: ''}
|
||||||
|
{ this.state.lastAction === 'invalid_configuration' ?
|
||||||
|
<div className="alert alert-danger">
|
||||||
|
<i className="glyphicon glyphicon-exclamation-sign" style={{'marginRight': '5px'}}/>
|
||||||
|
An invalid matrix configuration supplied, check selected fields.
|
||||||
|
</div>
|
||||||
|
: ''}
|
||||||
|
</div>
|
||||||
|
<div className="text-center">
|
||||||
|
<button onClick={this.onSubmit} className="btn btn-success btn-lg" style={{margin: '5px'}}>
|
||||||
|
Apply to configuration
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MatrixComponent;
|
|
@ -1,83 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import Form from 'react-jsonschema-form';
|
|
||||||
import {Col, Nav, NavItem} from 'react-bootstrap';
|
|
||||||
import Checkbox from '../ui-components/checkbox'
|
|
||||||
import Tooltip from 'react-tooltip-lite'
|
|
||||||
import AuthComponent from '../AuthComponent';
|
|
||||||
import ReactTable from "react-table";
|
|
||||||
import 'filepond/dist/filepond.min.css';
|
|
||||||
import '../../styles/Tooltip.scss';
|
|
||||||
|
|
||||||
|
|
||||||
let renderTechnique = function (technique) {
|
|
||||||
console.log(technique);
|
|
||||||
if (technique == null){
|
|
||||||
return (<div></div>)
|
|
||||||
} else {
|
|
||||||
return (<Tooltip content={technique.description} direction="down"><Checkbox>
|
|
||||||
{technique.title}</Checkbox> </Tooltip>)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Finds which attack type has most techniques and returns that number
|
|
||||||
let findMaxTechniques = function (data){
|
|
||||||
let maxLen = 0;
|
|
||||||
data.forEach(function(techType) {
|
|
||||||
if (Object.keys(techType.properties).length > maxLen){
|
|
||||||
maxLen = Object.keys(techType.properties).length
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return maxLen
|
|
||||||
};
|
|
||||||
|
|
||||||
let parseTechniques = function (data, maxLen) {
|
|
||||||
let techniques = [];
|
|
||||||
// Create rows with attack techniques
|
|
||||||
for (let i = 0; i < maxLen; i++) {
|
|
||||||
let row = {};
|
|
||||||
data.forEach(function(techType){
|
|
||||||
let rowColumn = {};
|
|
||||||
rowColumn.techName = techType.title;
|
|
||||||
if (i <= Object.keys(techType.properties).length) {
|
|
||||||
rowColumn.technique = Object.values(techType.properties)[i];
|
|
||||||
} else {
|
|
||||||
rowColumn.technique = null
|
|
||||||
}
|
|
||||||
row[rowColumn.techName] = rowColumn
|
|
||||||
});
|
|
||||||
techniques.push(row)
|
|
||||||
}
|
|
||||||
return techniques;
|
|
||||||
};
|
|
||||||
|
|
||||||
class MatrixComponent extends AuthComponent {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.maxTechniques = findMaxTechniques(Object.values(this.props.configuration));
|
|
||||||
this.data = parseTechniques(Object.values(this.props.configuration), this.maxTechniques);
|
|
||||||
}
|
|
||||||
|
|
||||||
getColumns() {
|
|
||||||
return Object.keys(this.data[0]).map((key)=>{
|
|
||||||
return {
|
|
||||||
Header: key,
|
|
||||||
id: key,
|
|
||||||
accessor: x => renderTechnique(x[key].technique),
|
|
||||||
style: { 'white-space': 'unset' }
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
console.log(this.data);
|
|
||||||
let columns = this.getColumns();
|
|
||||||
return (<ReactTable
|
|
||||||
columns={columns}
|
|
||||||
data={this.data}
|
|
||||||
showPagination={false}
|
|
||||||
defaultPageSize={this.maxTechniques}
|
|
||||||
/>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MatrixComponent;
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import AuthComponent from '../AuthComponent';
|
import AuthComponent from '../AuthComponent';
|
||||||
import 'filepond/dist/filepond.min.css';
|
import 'filepond/dist/filepond.min.css';
|
||||||
import MatrixComponent from '../attck/MatrixComponent'
|
import MatrixComponent from '../attack/MatrixComponent'
|
||||||
|
|
||||||
class AttckComponent extends AuthComponent {
|
class AttckComponent extends AuthComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
@ -12,14 +12,13 @@ class AttckComponent extends AuthComponent {
|
||||||
// set schema from server
|
// set schema from server
|
||||||
this.state = {
|
this.state = {
|
||||||
configuration: {},
|
configuration: {},
|
||||||
lastAction: 'none',
|
|
||||||
sections: [],
|
sections: [],
|
||||||
selectedSection: 'ATT&CK matrix',
|
selectedSection: 'ATT&CK matrix',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.authFetch('/api/attck')
|
this.authFetch('/api/attack')
|
||||||
.then(res => res.json())
|
.then(res => res.json())
|
||||||
.then(res => {
|
.then(res => {
|
||||||
let sections = [];
|
let sections = [];
|
||||||
|
@ -36,11 +35,7 @@ class AttckComponent extends AuthComponent {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
let content;
|
let content;
|
||||||
if (Object.keys(this.state.configuration).length === 0) {
|
content = (<MatrixComponent configuration={this.state.configuration} />);
|
||||||
content = (<h1>Fetching configuration...</h1>);
|
|
||||||
} else {
|
|
||||||
content = (<MatrixComponent configuration={this.state.configuration} />);
|
|
||||||
}
|
|
||||||
return <div>{content}</div>;
|
return <div>{content}</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -515,3 +515,8 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Attack config page */
|
||||||
|
.attack-matrix .messages {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// colors
|
// colors
|
||||||
$light-grey: #EAF4F4;
|
$light-grey: #EAF4F4;
|
||||||
$medium-grey: #7B9EA8;
|
$medium-grey: #7B9EA8;
|
||||||
$dark-grey: #7B9EA8;
|
$dark-grey: #7a7d7b;
|
||||||
$green: #44CF6C;
|
$green: #44CF6C;
|
||||||
$black: #000000;
|
$black: #000000;
|
||||||
|
|
||||||
|
@ -33,6 +33,12 @@ $black: #000000;
|
||||||
fill: $black;
|
fill: $black;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.blocked {
|
||||||
|
background-color: $dark-grey;
|
||||||
|
color: $black;
|
||||||
|
fill: $black;
|
||||||
|
}
|
||||||
|
|
||||||
&.is-checked {
|
&.is-checked {
|
||||||
border: 1px solid $green;
|
border: 1px solid $green;
|
||||||
background-color: $green;
|
background-color: $green;
|
||||||
|
|
Loading…
Reference in New Issue