From 104dc4fb5c423e5221e2897ebacdff4e37b28b86 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 10 Jun 2020 13:01:22 +0200 Subject: [PATCH 01/55] Add CascadingRule CRD --- operator/PROJECT | 3 + .../apis/cascading/v1/cascadingrule_types.go | 77 ++++++++++++ .../apis/cascading/v1/groupversion_info.go | 36 ++++++ .../cascading/v1/zz_generated.deepcopy.go | 114 ++++++++++++++++++ ...ental.securecodebox.io_cascadingrules.yaml | 93 ++++++++++++++ operator/config/crd/kustomization.yaml | 3 + .../cainjection_in_cascadingrules.yaml | 8 ++ .../patches/webhook_in_cascadingrules.yaml | 17 +++ .../rbac/cascadingrule_editor_role.yaml | 24 ++++ .../rbac/cascadingrule_viewer_role.yaml | 20 +++ .../samples/cascading_v1_cascadingrule.yaml | 7 ++ operator/go.sum | 1 + operator/main.go | 2 + 13 files changed, 405 insertions(+) create mode 100644 operator/apis/cascading/v1/cascadingrule_types.go create mode 100644 operator/apis/cascading/v1/groupversion_info.go create mode 100644 operator/apis/cascading/v1/zz_generated.deepcopy.go create mode 100644 operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml create mode 100644 operator/config/crd/patches/cainjection_in_cascadingrules.yaml create mode 100644 operator/config/crd/patches/webhook_in_cascadingrules.yaml create mode 100644 operator/config/rbac/cascadingrule_editor_role.yaml create mode 100644 operator/config/rbac/cascadingrule_viewer_role.yaml create mode 100644 operator/config/samples/cascading_v1_cascadingrule.yaml diff --git a/operator/PROJECT b/operator/PROJECT index 33eef2b8..a0c68343 100644 --- a/operator/PROJECT +++ b/operator/PROJECT @@ -20,4 +20,7 @@ resources: - group: targets kind: Host version: v1 +- group: cascading + kind: CascadingRule + version: v1 version: "2" diff --git a/operator/apis/cascading/v1/cascadingrule_types.go b/operator/apis/cascading/v1/cascadingrule_types.go new file mode 100644 index 00000000..dcc9f76c --- /dev/null +++ b/operator/apis/cascading/v1/cascadingrule_types.go @@ -0,0 +1,77 @@ +/* +Copyright 2020 iteratec GmbH. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + executionv1 "github.com/secureCodeBox/secureCodeBox-v2-alpha/operator/apis/execution/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// CascadingRuleSpec defines the desired state of CascadingRule +type CascadingRuleSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Foo is an example field of CascadingRule. Edit CascadingRule_types.go to remove/update + Matches []MatchesRule `json:"matches"` + ScanSpec executionv1.ScanSpec `json:"scanSpec"` +} + +// MatchesRule is a generic map which is used to model the structure of a finding for which the CascadingRule should take effect +type MatchesRule struct { + Name string `json:"name,omitempty"` + Category string `json:"category,omitempty"` + Description string `json:"description,omitempty"` + Location string `json:"location,omitempty"` + Severity string `json:"severity,omitempty"` + OsiLayer string `json:"osi_layer,omitempty"` + Attributes map[string]intstr.IntOrString `json:"attributes,omitempty"` +} + +// CascadingRuleStatus defines the observed state of CascadingRule +type CascadingRuleStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +// +kubebuilder:object:root=true + +// CascadingRule is the Schema for the cascadingrules API +type CascadingRule struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec CascadingRuleSpec `json:"spec,omitempty"` + Status CascadingRuleStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// CascadingRuleList contains a list of CascadingRule +type CascadingRuleList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CascadingRule `json:"items"` +} + +func init() { + SchemeBuilder.Register(&CascadingRule{}, &CascadingRuleList{}) +} diff --git a/operator/apis/cascading/v1/groupversion_info.go b/operator/apis/cascading/v1/groupversion_info.go new file mode 100644 index 00000000..4cf5a62b --- /dev/null +++ b/operator/apis/cascading/v1/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2020 iteratec GmbH. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package v1 contains API Schema definitions for the cascading v1 API group +// +kubebuilder:object:generate=true +// +groupName=cascading.experimental.securecodebox.io +package v1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "cascading.experimental.securecodebox.io", Version: "v1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/operator/apis/cascading/v1/zz_generated.deepcopy.go b/operator/apis/cascading/v1/zz_generated.deepcopy.go new file mode 100644 index 00000000..545e5df8 --- /dev/null +++ b/operator/apis/cascading/v1/zz_generated.deepcopy.go @@ -0,0 +1,114 @@ +// +build !ignore_autogenerated + +/* +Copyright 2020 iteratec GmbH. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Code generated by controller-gen. DO NOT EDIT. + +package v1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CascadingRule) DeepCopyInto(out *CascadingRule) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CascadingRule. +func (in *CascadingRule) DeepCopy() *CascadingRule { + if in == nil { + return nil + } + out := new(CascadingRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CascadingRule) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CascadingRuleList) DeepCopyInto(out *CascadingRuleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CascadingRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CascadingRuleList. +func (in *CascadingRuleList) DeepCopy() *CascadingRuleList { + if in == nil { + return nil + } + out := new(CascadingRuleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CascadingRuleList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CascadingRuleSpec) DeepCopyInto(out *CascadingRuleSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CascadingRuleSpec. +func (in *CascadingRuleSpec) DeepCopy() *CascadingRuleSpec { + if in == nil { + return nil + } + out := new(CascadingRuleSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CascadingRuleStatus) DeepCopyInto(out *CascadingRuleStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CascadingRuleStatus. +func (in *CascadingRuleStatus) DeepCopy() *CascadingRuleStatus { + if in == nil { + return nil + } + out := new(CascadingRuleStatus) + in.DeepCopyInto(out) + return out +} diff --git a/operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml b/operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml new file mode 100644 index 00000000..a34d53e3 --- /dev/null +++ b/operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml @@ -0,0 +1,93 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.4 + creationTimestamp: null + name: cascadingrules.cascading.experimental.securecodebox.io +spec: + group: cascading.experimental.securecodebox.io + names: + kind: CascadingRule + listKind: CascadingRuleList + plural: cascadingrules + singular: cascadingrule + scope: Namespaced + validation: + openAPIV3Schema: + description: CascadingRule is the Schema for the cascadingrules API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: CascadingRuleSpec defines the desired state of CascadingRule + properties: + matches: + description: Foo is an example field of CascadingRule. Edit CascadingRule_types.go + to remove/update + items: + description: MatchesRule is a generic map which is used to model the + structure of a finding for which the CascadingRule should take effect + properties: + attributes: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + category: + type: string + description: + type: string + location: + type: string + name: + type: string + osi_layer: + type: string + severity: + type: string + type: object + type: array + scanSpec: + description: ScanSpec defines the desired state of Scan + properties: + parameters: + items: + type: string + type: array + scanType: + type: string + type: object + required: + - matches + - scanSpec + type: object + status: + description: CascadingRuleStatus defines the observed state of CascadingRule + type: object + type: object + version: v1 + versions: + - name: v1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/operator/config/crd/kustomization.yaml b/operator/config/crd/kustomization.yaml index f3f614c1..7a1e9496 100644 --- a/operator/config/crd/kustomization.yaml +++ b/operator/config/crd/kustomization.yaml @@ -8,6 +8,7 @@ resources: - bases/execution.experimental.securecodebox.io_parsedefinitions.yaml - bases/execution.experimental.securecodebox.io_scheduledscans.yaml - bases/targets.experimental.securecodebox.io_hosts.yaml +- bases/cascading.experimental.securecodebox.io_cascadingrules.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -19,6 +20,7 @@ patchesStrategicMerge: #- patches/webhook_in_parsedefinitions.yaml #- patches/webhook_in_scheduledscans.yaml #- patches/webhook_in_hosts.yaml +#- patches/webhook_in_cascadingrules.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. @@ -29,6 +31,7 @@ patchesStrategicMerge: #- patches/cainjection_in_parsedefinitions.yaml #- patches/cainjection_in_scheduledscans.yaml #- patches/cainjection_in_hosts.yaml +#- patches/cainjection_in_cascadingrules.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/operator/config/crd/patches/cainjection_in_cascadingrules.yaml b/operator/config/crd/patches/cainjection_in_cascadingrules.yaml new file mode 100644 index 00000000..95953e4a --- /dev/null +++ b/operator/config/crd/patches/cainjection_in_cascadingrules.yaml @@ -0,0 +1,8 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: cascadingrules.cascading.experimental.securecodebox.io diff --git a/operator/config/crd/patches/webhook_in_cascadingrules.yaml b/operator/config/crd/patches/webhook_in_cascadingrules.yaml new file mode 100644 index 00000000..bc9c0933 --- /dev/null +++ b/operator/config/crd/patches/webhook_in_cascadingrules.yaml @@ -0,0 +1,17 @@ +# The following patch enables conversion webhook for CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: cascadingrules.cascading.experimental.securecodebox.io +spec: + conversion: + strategy: Webhook + webhookClientConfig: + # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, + # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) + caBundle: Cg== + service: + namespace: system + name: webhook-service + path: /convert diff --git a/operator/config/rbac/cascadingrule_editor_role.yaml b/operator/config/rbac/cascadingrule_editor_role.yaml new file mode 100644 index 00000000..25560f60 --- /dev/null +++ b/operator/config/rbac/cascadingrule_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit cascadingrules. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cascadingrule-editor-role +rules: +- apiGroups: + - cascading.experimental.securecodebox.io + resources: + - cascadingrules + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - cascading.experimental.securecodebox.io + resources: + - cascadingrules/status + verbs: + - get diff --git a/operator/config/rbac/cascadingrule_viewer_role.yaml b/operator/config/rbac/cascadingrule_viewer_role.yaml new file mode 100644 index 00000000..9a3bffe1 --- /dev/null +++ b/operator/config/rbac/cascadingrule_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view cascadingrules. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cascadingrule-viewer-role +rules: +- apiGroups: + - cascading.experimental.securecodebox.io + resources: + - cascadingrules + verbs: + - get + - list + - watch +- apiGroups: + - cascading.experimental.securecodebox.io + resources: + - cascadingrules/status + verbs: + - get diff --git a/operator/config/samples/cascading_v1_cascadingrule.yaml b/operator/config/samples/cascading_v1_cascadingrule.yaml new file mode 100644 index 00000000..f5d176ac --- /dev/null +++ b/operator/config/samples/cascading_v1_cascadingrule.yaml @@ -0,0 +1,7 @@ +apiVersion: cascading.experimental.securecodebox.io/v1 +kind: CascadingRule +metadata: + name: cascadingrule-sample +spec: + # Add fields here + foo: bar diff --git a/operator/go.sum b/operator/go.sum index a4593826..5643d29d 100644 --- a/operator/go.sum +++ b/operator/go.sum @@ -254,6 +254,7 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT github.com/remyoudompheng/bigfft v0.0.0-20170806203942-52369c62f446/go.mod h1:uYEyJGbgTkfkS4+E/PavXkNJcbFIpEtjt2B0KDQ5+9M= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/secureCodeBox/secureCodeBox-v2-alpha v0.0.0-20200526134830-4f0a0ddfccc0 h1:cmPDEtYAxHRmOmMuKUKe90RjJUjALqiXnJtPB4VGe44= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= diff --git a/operator/main.go b/operator/main.go index 8c393b9c..18921b75 100644 --- a/operator/main.go +++ b/operator/main.go @@ -26,6 +26,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/log/zap" + cascadingv1 "github.com/secureCodeBox/secureCodeBox-v2-alpha/operator/apis/cascading/v1" executionv1 "github.com/secureCodeBox/secureCodeBox-v2-alpha/operator/apis/execution/v1" targetsv1 "github.com/secureCodeBox/secureCodeBox-v2-alpha/operator/apis/targets/v1" executioncontroller "github.com/secureCodeBox/secureCodeBox-v2-alpha/operator/controllers/execution" @@ -43,6 +44,7 @@ func init() { _ = executionv1.AddToScheme(scheme) _ = targetsv1.AddToScheme(scheme) + _ = cascadingv1.AddToScheme(scheme) // +kubebuilder:scaffold:scheme } From e909d58d68c5183b23c9660f06e83c4c73da64b7 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 10 Jun 2020 13:01:36 +0200 Subject: [PATCH 02/55] #33 Add WIP declarative subsequent hook implementation --- .../.dockerignore | 1 + hooks/declarative-subsequent-scans/.gitignore | 1 + .../declarative-subsequent-scans/.helmignore | 30 + hooks/declarative-subsequent-scans/Chart.lock | 3 + hooks/declarative-subsequent-scans/Chart.yaml | 11 + hooks/declarative-subsequent-scans/Dockerfile | 5 + hooks/declarative-subsequent-scans/hook.js | 46 + .../declarative-subsequent-scans/hook.test.js | 82 + .../package-lock.json | 5184 +++++++++++++++++ .../declarative-subsequent-scans/package.json | 19 + .../scan-helpers.js | 68 + .../templates/NOTES.txt | 2 + .../templates/_helpers.tpl | 52 + .../declerative-subsequent-scans-hook.yaml | 19 + .../tmpCascadingRules/sslyze.yaml | 16 + .../declarative-subsequent-scans/values.yaml | 9 + 16 files changed, 5548 insertions(+) create mode 100644 hooks/declarative-subsequent-scans/.dockerignore create mode 100644 hooks/declarative-subsequent-scans/.gitignore create mode 100644 hooks/declarative-subsequent-scans/.helmignore create mode 100644 hooks/declarative-subsequent-scans/Chart.lock create mode 100644 hooks/declarative-subsequent-scans/Chart.yaml create mode 100644 hooks/declarative-subsequent-scans/Dockerfile create mode 100644 hooks/declarative-subsequent-scans/hook.js create mode 100644 hooks/declarative-subsequent-scans/hook.test.js create mode 100644 hooks/declarative-subsequent-scans/package-lock.json create mode 100644 hooks/declarative-subsequent-scans/package.json create mode 100644 hooks/declarative-subsequent-scans/scan-helpers.js create mode 100644 hooks/declarative-subsequent-scans/templates/NOTES.txt create mode 100644 hooks/declarative-subsequent-scans/templates/_helpers.tpl create mode 100644 hooks/declarative-subsequent-scans/templates/declerative-subsequent-scans-hook.yaml create mode 100644 hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml create mode 100644 hooks/declarative-subsequent-scans/values.yaml diff --git a/hooks/declarative-subsequent-scans/.dockerignore b/hooks/declarative-subsequent-scans/.dockerignore new file mode 100644 index 00000000..40b878db --- /dev/null +++ b/hooks/declarative-subsequent-scans/.dockerignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/hooks/declarative-subsequent-scans/.gitignore b/hooks/declarative-subsequent-scans/.gitignore new file mode 100644 index 00000000..b512c09d --- /dev/null +++ b/hooks/declarative-subsequent-scans/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/hooks/declarative-subsequent-scans/.helmignore b/hooks/declarative-subsequent-scans/.helmignore new file mode 100644 index 00000000..676a3554 --- /dev/null +++ b/hooks/declarative-subsequent-scans/.helmignore @@ -0,0 +1,30 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ +# Node.js files +node_modules/* +package.json +package-lock.json +src/* +config/* +Dockerfile +.dockerignore \ No newline at end of file diff --git a/hooks/declarative-subsequent-scans/Chart.lock b/hooks/declarative-subsequent-scans/Chart.lock new file mode 100644 index 00000000..eb7f3a24 --- /dev/null +++ b/hooks/declarative-subsequent-scans/Chart.lock @@ -0,0 +1,3 @@ +dependencies: [] +digest: sha256:643d5437104296e21d906ecb15b2c96ad278f20cfc4af53b12bb6069bd853726 +generated: "2020-05-26T16:56:03.119255+02:00" diff --git a/hooks/declarative-subsequent-scans/Chart.yaml b/hooks/declarative-subsequent-scans/Chart.yaml new file mode 100644 index 00000000..c04e7d9e --- /dev/null +++ b/hooks/declarative-subsequent-scans/Chart.yaml @@ -0,0 +1,11 @@ +apiVersion: v2 +name: declarative-subsequent-scans +description: Starts possible subsequent security scans based on findings (e.g. open ports found by NMAP or subdomains found by AMASS). + +type: application + +version: 0.1.0 + +appVersion: latest + +dependencies: [] diff --git a/hooks/declarative-subsequent-scans/Dockerfile b/hooks/declarative-subsequent-scans/Dockerfile new file mode 100644 index 00000000..880f259c --- /dev/null +++ b/hooks/declarative-subsequent-scans/Dockerfile @@ -0,0 +1,5 @@ +# This image doesn't install the hooks dependencies, as it only has the @kubernetes/client-node dependencies which is already installed via the hook-sdk + +FROM scbexperimental/hook-sdk-nodejs:latest +WORKDIR /home/app/hook-wrapper/hook/ +COPY --chown=app:app hook.js scan-helpers.js ./ diff --git a/hooks/declarative-subsequent-scans/hook.js b/hooks/declarative-subsequent-scans/hook.js new file mode 100644 index 00000000..0eac9afe --- /dev/null +++ b/hooks/declarative-subsequent-scans/hook.js @@ -0,0 +1,46 @@ +const { startSubsequentSecureCodeBoxScan } = require("./scan-helpers"); +const isMatch = require("lodash.ismatch"); + +async function handle({ scan, getFindings }) { + const findings = await getFindings(); + const cascadingRules = await getCascadingRules(); + + const cascadingScans = getCascadingScans(findings, cascadingRules); + + for (const { scanType, parameters } of cascadingScans) { + await startSubsequentSecureCodeBoxScan({ + parentScan: scan, + scanType, + parameters, + }); + } +} + +async function getCascadingRules() { + // Todo: Get all CascadingRules of the current Namespace via k8s api + return []; +} + +// Todo remove eslint disable +// eslint-disable-next-line no-unused-vars +function getCascadingScans(findings, cascadingRules) { + const cascadingScans = []; + + for (const cascadingRule of cascadingRules) { + for (const finding of findings) { + const matches = cascadingRule.spec.matches.some((matchesRule) => + isMatch(finding, matchesRule) + ); + + if (matches) { + // Todo templating + cascadingScans.push(cascadingRule.spec.scanSpec); + } + } + } + + return cascadingScans; +} + +module.exports.getCascadingScans = getCascadingScans; +module.exports.handle = handle; diff --git a/hooks/declarative-subsequent-scans/hook.test.js b/hooks/declarative-subsequent-scans/hook.test.js new file mode 100644 index 00000000..27861950 --- /dev/null +++ b/hooks/declarative-subsequent-scans/hook.test.js @@ -0,0 +1,82 @@ +const { getCascadingScans } = require("./hook"); + +test("Should create subsequent scans for open HTTPS ports (NMAP findings)", () => { + const findings = [ + { + name: "Port 443 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 443, + service: "https" + } + } + ]; + + const cascadingRules = [ + { + apiVersion: "cascading.experimental.securecodebox.io/v1", + kind: "CascadingRule", + metadata: { + name: "tls-scans" + }, + spec: { + matches: [ + { + category: "Open Port", + attributes: { + port: 443, + service: "https" + } + }, + { + category: "Open Port", + attributes: { + service: "https" + } + } + ], + scanSpec: { + name: "sslyze", + parameters: ["--regular", "{attributes.hostname}"] + } + } + } + ]; + + const cascadedScans = getCascadingScans(findings, cascadingRules); + + expect(cascadedScans).toMatchInlineSnapshot(` + Array [ + Object { + "name": "sslyze", + "parameters": Array [ + "--regular", + "{attributes.hostname}", + ], + }, + ] + `); +}); + +test("Should create no subsequent scans if there are no rules", () => { + const findings = [ + { + name: "Port 443 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 443, + service: "https" + } + } + ]; + + const cascadingRules = []; + + const cascadedScans = getCascadingScans(findings, cascadingRules); + + expect(cascadedScans).toMatchInlineSnapshot(`Array []`); +}); diff --git a/hooks/declarative-subsequent-scans/package-lock.json b/hooks/declarative-subsequent-scans/package-lock.json new file mode 100644 index 00000000..bb4ce66d --- /dev/null +++ b/hooks/declarative-subsequent-scans/package-lock.json @@ -0,0 +1,5184 @@ +{ + "name": "declarative-subsequent-scans", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/core": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.9.6.tgz", + "integrity": "sha512-nD3deLvbsApbHAHttzIssYqgb883yU/d9roe4RZymBCDaZryMJDbptVpEpeQuRh4BJ+SYI8le9YGxKvFEvl1Wg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.6", + "@babel/helper-module-transforms": "^7.9.0", + "@babel/helpers": "^7.9.6", + "@babel/parser": "^7.9.6", + "@babel/template": "^7.8.6", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.1", + "json5": "^2.1.2", + "lodash": "^4.17.13", + "resolve": "^1.3.2", + "semver": "^5.4.1", + "source-map": "^0.5.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/generator": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz", + "integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==", + "dev": true, + "requires": { + "@babel/types": "^7.9.6", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz", + "integrity": "sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.9.5" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz", + "integrity": "sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-imports": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz", + "integrity": "sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-module-transforms": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz", + "integrity": "sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.8.3", + "@babel/helper-replace-supers": "^7.8.6", + "@babel/helper-simple-access": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/template": "^7.8.6", + "@babel/types": "^7.9.0", + "lodash": "^4.17.13" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz", + "integrity": "sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz", + "integrity": "sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==", + "dev": true + }, + "@babel/helper-replace-supers": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz", + "integrity": "sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.8.3", + "@babel/helper-optimise-call-expression": "^7.8.3", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6" + } + }, + "@babel/helper-simple-access": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz", + "integrity": "sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==", + "dev": true, + "requires": { + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.9.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", + "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==", + "dev": true + }, + "@babel/helpers": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.6.tgz", + "integrity": "sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw==", + "dev": true, + "requires": { + "@babel/template": "^7.8.3", + "@babel/traverse": "^7.9.6", + "@babel/types": "^7.9.6" + } + }, + "@babel/highlight": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", + "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz", + "integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==", + "dev": true + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.8.3.tgz", + "integrity": "sha512-UcAyQWg2bAN647Q+O811tG9MrJ38Z10jjhQdKNAL8fsyPzE3cCN/uT+f55cFVY4aGO4jqJAvmqsuY3GQDwAoXg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.8.3.tgz", + "integrity": "sha512-Zpg2Sgc++37kuFl6ppq2Q7Awc6E6AIW671x5PY8E/f7MCIyPPGK/EoeZXvvY3P42exZ3Q4/t3YOzP/HiN79jDg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz", + "integrity": "sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/template": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.6.tgz", + "integrity": "sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.6", + "@babel/types": "^7.8.6" + } + }, + "@babel/traverse": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz", + "integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.9.6", + "@babel/helper-function-name": "^7.9.5", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.9.6", + "@babel/types": "^7.9.6", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + } + }, + "@babel/types": { + "version": "7.9.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz", + "integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.9.5", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@cnakazawa/watch": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", + "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", + "dev": true, + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + } + }, + "@istanbuljs/schema": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", + "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "dev": true + }, + "@jest/console": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-25.5.0.tgz", + "integrity": "sha512-T48kZa6MK1Y6k4b89sexwmSF4YLeZS/Udqg3Jj3jG/cHH+N/sLFCEoXEDMOKugJQ9FxPN1osxIknvKkxt6MKyw==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "jest-message-util": "^25.5.0", + "jest-util": "^25.5.0", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-25.5.4.tgz", + "integrity": "sha512-3uSo7laYxF00Dg/DMgbn4xMJKmDdWvZnf89n8Xj/5/AeQ2dOQmn6b6Hkj/MleyzZWXpwv+WSdYWl4cLsy2JsoA==", + "dev": true, + "requires": { + "@jest/console": "^25.5.0", + "@jest/reporters": "^25.5.1", + "@jest/test-result": "^25.5.0", + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", + "ansi-escapes": "^4.2.1", + "chalk": "^3.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-changed-files": "^25.5.0", + "jest-config": "^25.5.4", + "jest-haste-map": "^25.5.1", + "jest-message-util": "^25.5.0", + "jest-regex-util": "^25.2.6", + "jest-resolve": "^25.5.1", + "jest-resolve-dependencies": "^25.5.4", + "jest-runner": "^25.5.4", + "jest-runtime": "^25.5.4", + "jest-snapshot": "^25.5.1", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", + "jest-watcher": "^25.5.0", + "micromatch": "^4.0.2", + "p-each-series": "^2.1.0", + "realpath-native": "^2.0.0", + "rimraf": "^3.0.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "@jest/environment": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-25.5.0.tgz", + "integrity": "sha512-U2VXPEqL07E/V7pSZMSQCvV5Ea4lqOlT+0ZFijl/i316cRMHvZ4qC+jBdryd+lmRetjQo0YIQr6cVPNxxK87mA==", + "dev": true, + "requires": { + "@jest/fake-timers": "^25.5.0", + "@jest/types": "^25.5.0", + "jest-mock": "^25.5.0" + } + }, + "@jest/fake-timers": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-25.5.0.tgz", + "integrity": "sha512-9y2+uGnESw/oyOI3eww9yaxdZyHq7XvprfP/eeoCsjqKYts2yRlsHS/SgjPDV8FyMfn2nbMy8YzUk6nyvdLOpQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-util": "^25.5.0", + "lolex": "^5.0.0" + } + }, + "@jest/globals": { + "version": "25.5.2", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-25.5.2.tgz", + "integrity": "sha512-AgAS/Ny7Q2RCIj5kZ+0MuKM1wbF0WMLxbCVl/GOMoCNbODRdJ541IxJ98xnZdVSZXivKpJlNPIWa3QmY0l4CXA==", + "dev": true, + "requires": { + "@jest/environment": "^25.5.0", + "@jest/types": "^25.5.0", + "expect": "^25.5.0" + } + }, + "@jest/reporters": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-25.5.1.tgz", + "integrity": "sha512-3jbd8pPDTuhYJ7vqiHXbSwTJQNavczPs+f1kRprRDxETeE3u6srJ+f0NPuwvOmk+lmunZzPkYWIFZDLHQPkviw==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.2", + "graceful-fs": "^4.2.4", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "jest-haste-map": "^25.5.1", + "jest-resolve": "^25.5.1", + "jest-util": "^25.5.0", + "jest-worker": "^25.5.0", + "node-notifier": "^6.0.0", + "slash": "^3.0.0", + "source-map": "^0.6.0", + "string-length": "^3.1.0", + "terminal-link": "^2.0.0", + "v8-to-istanbul": "^4.1.3" + } + }, + "@jest/source-map": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-25.5.0.tgz", + "integrity": "sha512-eIGx0xN12yVpMcPaVpjXPnn3N30QGJCJQSkEDUt9x1fI1Gdvb07Ml6K5iN2hG7NmMP6FDmtPEssE3z6doOYUwQ==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + } + }, + "@jest/test-result": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-25.5.0.tgz", + "integrity": "sha512-oV+hPJgXN7IQf/fHWkcS99y0smKLU2czLBJ9WA0jHITLst58HpQMtzSYxzaBvYc6U5U6jfoMthqsUlUlbRXs0A==", + "dev": true, + "requires": { + "@jest/console": "^25.5.0", + "@jest/types": "^25.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-25.5.4.tgz", + "integrity": "sha512-pTJGEkSeg1EkCO2YWq6hbFvKNXk8ejqlxiOg1jBNLnWrgXOkdY6UmqZpwGFXNnRt9B8nO1uWMzLLZ4eCmhkPNA==", + "dev": true, + "requires": { + "@jest/test-result": "^25.5.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^25.5.1", + "jest-runner": "^25.5.4", + "jest-runtime": "^25.5.4" + } + }, + "@jest/transform": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-25.5.1.tgz", + "integrity": "sha512-Y8CEoVwXb4QwA6Y/9uDkn0Xfz0finGkieuV0xkdF9UtZGJeLukD5nLkaVrVsODB1ojRWlaoD0AJZpVHCSnJEvg==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^25.5.0", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^3.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^25.5.1", + "jest-regex-util": "^25.2.6", + "jest-util": "^25.5.0", + "micromatch": "^4.0.2", + "pirates": "^4.0.1", + "realpath-native": "^2.0.0", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + } + }, + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "@kubernetes/client-node": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@kubernetes/client-node/-/client-node-0.12.0.tgz", + "integrity": "sha512-u57q5IaZl91f7YZoZOsgCa31hHyowHxFG88XZXd8arI8heSxbdHWHineo/8mLZbeSbHkge9Awae1stQZzuTnjg==", + "requires": { + "@types/js-yaml": "^3.12.1", + "@types/node": "^10.12.0", + "@types/request": "^2.47.1", + "@types/underscore": "^1.8.9", + "@types/ws": "^6.0.1", + "byline": "^5.0.0", + "execa": "1.0.0", + "isomorphic-ws": "^4.0.1", + "js-yaml": "^3.13.1", + "jsonpath-plus": "^0.19.0", + "openid-client": "2.5.0", + "request": "^2.88.0", + "rfc4648": "^1.3.0", + "shelljs": "^0.8.2", + "tslib": "^1.9.3", + "underscore": "^1.9.1", + "ws": "^6.1.0" + }, + "dependencies": { + "@types/node": { + "version": "10.17.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.24.tgz", + "integrity": "sha512-5SCfvCxV74kzR3uWgTYiGxrd69TbT1I6+cMx1A5kEly/IVveJBimtAMlXiEyVFn5DvUFewQWxOOiJhlxeQwxgA==" + }, + "ws": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", + "requires": { + "async-limiter": "~1.0.0" + } + } + } + }, + "@sindresorhus/is": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", + "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==" + }, + "@sinonjs/commons": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz", + "integrity": "sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@types/babel__core": { + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.7.tgz", + "integrity": "sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz", + "integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", + "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.11.tgz", + "integrity": "sha512-ddHK5icION5U6q11+tV2f9Mo6CZVuT8GJKld2q9LqHSZbvLbH34Kcu2yFGckZut453+eQU6btIA3RihmnRgI+Q==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, + "@types/graceful-fs": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.3.tgz", + "integrity": "sha512-AiHRaEB50LQg0pZmm659vNBb9f4SJ0qrAnteuzhSeAUcJKxoYgEnprg/83kppCnc2zvtCKbdZry1a5pVY3lOTQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.2.tgz", + "integrity": "sha512-rsZg7eL+Xcxsxk2XlBt9KcG8nOp9iYdKCOikY9x2RFJCyOdNj4MKPQty0e8oZr29vVAzKXr1BmR+kZauti3o1w==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", + "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, + "@types/js-yaml": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.4.tgz", + "integrity": "sha512-fYMgzN+9e28R81weVN49inn/u798ruU91En1ZnGvSZzCRc5jXx9B2EDhlRaWmcO1RIxFHL8AajRXzxDuJu93+A==" + }, + "@types/node": { + "version": "14.0.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.5.tgz", + "integrity": "sha512-90hiq6/VqtQgX8Sp0EzeIsv3r+ellbGj4URKj5j30tLlZvRUpnAe9YbYnjl3pJM93GyXU0tghHhvXHq+5rnCKA==" + }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", + "dev": true + }, + "@types/prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==", + "dev": true + }, + "@types/request": { + "version": "2.48.5", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz", + "integrity": "sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ==", + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + }, + "dependencies": { + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + } + } + }, + "@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "dev": true + }, + "@types/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==" + }, + "@types/underscore": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@types/underscore/-/underscore-1.10.0.tgz", + "integrity": "sha512-ZAbqul7QAKpM2h1PFGa5ETN27ulmqtj0QviYHasw9LffvXZvVHuraOx/FOsIPPDNGZN0Qo1nASxxSfMYOtSoCw==" + }, + "@types/ws": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.4.tgz", + "integrity": "sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg==", + "requires": { + "@types/node": "*" + } + }, + "@types/yargs": { + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.5.tgz", + "integrity": "sha512-Dk/IDOPtOgubt/IaevIUbTgV7doaKkoorvOyYM2CMwuDyP89bekI7H4xLIwunNYiK9jhCkmc6pUrJk3cj2AB9w==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", + "dev": true + }, + "abab": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", + "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", + "dev": true + }, + "acorn": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.2.0.tgz", + "integrity": "sha512-apwXVmYVpQ34m/i71vrApRrRKCWQnZZF1+npOD0WV5xZFfwWOmKGQ2RWlfdy9vWITsenisM8M0Qeq8agcFHNiQ==", + "dev": true + }, + "acorn-globals": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", + "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", + "dev": true, + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + }, + "dependencies": { + "acorn": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "dev": true + } + } + }, + "acorn-walk": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", + "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", + "dev": true + }, + "aggregate-error": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-1.0.0.tgz", + "integrity": "sha1-iINE2tAiCnLjr1CQYRf0h3GSX6w=", + "requires": { + "clean-stack": "^1.0.0", + "indent-string": "^3.0.0" + } + }, + "ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "dev": true, + "requires": { + "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "dev": true + } + } + }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true + }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, + "async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "dev": true + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", + "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" + }, + "babel-jest": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-25.5.1.tgz", + "integrity": "sha512-9dA9+GmMjIzgPnYtkhBg73gOo/RHqPmLruP3BaGL4KEX3Dwz6pI8auSN8G8+iuEG90+GSswyKvslN+JYSaacaQ==", + "dev": true, + "requires": { + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", + "@types/babel__core": "^7.1.7", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^25.5.0", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", + "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^4.0.0", + "test-exclude": "^6.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.5.0.tgz", + "integrity": "sha512-u+/W+WAjMlvoocYGTwthAiQSxDcJAyHpQ6oWlHdFZaaN+Rlk8Q7iiwDPg2lN/FyJtAYnKjFxbn7xus4HCFkg5g==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.2.tgz", + "integrity": "sha512-u/8cS+dEiK1SFILbOC8/rUI3ml9lboKuuMvZ/4aQnQmhecQAgPw5ew066C1ObnEAUmlx7dv/s2z52psWEtLNiw==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-25.5.0.tgz", + "integrity": "sha512-8ZczygctQkBU+63DtSOKGh7tFL0CeCuz+1ieud9lJ1WPQ9O6A1a/r+LGn6Y705PA6whHQ3T1XuB/PmpfNYf8Fw==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^25.5.0", + "babel-preset-current-node-syntax": "^0.1.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "base64url": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", + "integrity": "sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==" + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, + "browser-resolve": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", + "dev": true, + "requires": { + "resolve": "1.1.7" + }, + "dependencies": { + "resolve": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", + "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", + "dev": true + } + } + }, + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "requires": { + "pako": "~1.0.5" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", + "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "byline": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", + "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=" + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "cacheable-request": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz", + "integrity": "sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=", + "requires": { + "clone-response": "1.0.2", + "get-stream": "3.0.0", + "http-cache-semantics": "3.8.1", + "keyv": "3.0.0", + "lowercase-keys": "1.0.0", + "normalize-url": "2.0.1", + "responselike": "1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "lowercase-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "integrity": "sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=" + } + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, + "requires": { + "rsvp": "^4.8.4" + } + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "clean-stack": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-1.3.0.tgz", + "integrity": "sha1-noIVAa6XmYbEax1m0tQy2y/UrjE=" + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", + "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "cssom": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", + "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "requires": { + "mimic-response": "^1.0.0" + } + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "deepmerge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "dev": true + }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff-sequences": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", + "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", + "dev": true + }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "^4.0.2" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escodegen": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.1.tgz", + "integrity": "sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==", + "dev": true, + "requires": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "exec-sh": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", + "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", + "dev": true + }, + "execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "requires": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "expect": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-25.5.0.tgz", + "integrity": "sha512-w7KAXo0+6qqZZhovCaBVPSIqQp7/UTcx4M9uKt2m6pd2VB1voyC8JizLRqeEqud3AAVP02g+hbErDu5gu64tlA==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-styles": "^4.0.0", + "jest-get-type": "^25.2.6", + "jest-matcher-utils": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-regex-util": "^25.2.6" + } + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "fb-watchman": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", + "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "from2": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "gensync": { + "version": "1.0.0-beta.1", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.1.tgz", + "integrity": "sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "got": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/got/-/got-8.3.2.tgz", + "integrity": "sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==", + "requires": { + "@sindresorhus/is": "^0.7.0", + "cacheable-request": "^2.1.1", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "into-stream": "^3.1.0", + "is-retry-allowed": "^1.1.0", + "isurl": "^1.0.0-alpha5", + "lowercase-keys": "^1.0.0", + "mimic-response": "^1.0.0", + "p-cancelable": "^0.4.0", + "p-timeout": "^2.0.1", + "pify": "^3.0.0", + "safe-buffer": "^5.1.1", + "timed-out": "^4.0.1", + "url-parse-lax": "^3.0.0", + "url-to-options": "^1.0.1" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + } + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true, + "optional": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "has-symbol-support-x": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", + "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==" + }, + "has-to-string-tag-x": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", + "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", + "requires": { + "has-symbol-support-x": "^1.4.1" + } + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", + "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==" + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "dev": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "import-local": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", + "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" + }, + "into-stream": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/into-stream/-/into-stream-3.1.0.tgz", + "integrity": "sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=", + "requires": { + "from2": "^2.1.1", + "p-is-promise": "^1.1.0" + } + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "is-docker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.0.0.tgz", + "integrity": "sha512-pJEdRugimx4fBMra5z2/5iRdZ63OhYV0vr0Dwm5+xtW4D1FvRkB8hamMIhnWfyJeDdyr/aa7BDyNbtG38VxgoQ==", + "dev": true, + "optional": true + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", + "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=" + }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" + }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true + }, + "is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "optional": true, + "requires": { + "is-docker": "^2.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==" + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul-lib-coverage": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", + "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "requires": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + } + }, + "istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", + "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", + "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "isurl": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", + "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "requires": { + "has-to-string-tag-x": "^1.2.0", + "is-object": "^1.0.1" + } + }, + "jest": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest/-/jest-25.5.4.tgz", + "integrity": "sha512-hHFJROBTqZahnO+X+PMtT6G2/ztqAZJveGqz//FnWWHurizkD05PQGzRZOhF3XP6z7SJmL+5tCfW8qV06JypwQ==", + "dev": true, + "requires": { + "@jest/core": "^25.5.4", + "import-local": "^3.0.2", + "jest-cli": "^25.5.4" + }, + "dependencies": { + "jest-cli": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-25.5.4.tgz", + "integrity": "sha512-rG8uJkIiOUpnREh1768/N3n27Cm+xPFkSNFO91tgg+8o2rXeVLStz+vkXkGr4UtzH6t1SNbjwoiswd7p4AhHTw==", + "dev": true, + "requires": { + "@jest/core": "^25.5.4", + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "import-local": "^3.0.2", + "is-ci": "^2.0.0", + "jest-config": "^25.5.4", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", + "prompts": "^2.0.1", + "realpath-native": "^2.0.0", + "yargs": "^15.3.1" + } + } + } + }, + "jest-changed-files": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-25.5.0.tgz", + "integrity": "sha512-EOw9QEqapsDT7mKF162m8HFzRPbmP8qJQny6ldVOdOVBz3ACgPm/1nAn5fPQ/NDaYhX/AHkrGwwkCncpAVSXcw==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "execa": "^3.2.0", + "throat": "^5.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-3.4.0.tgz", + "integrity": "sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "p-finally": "^2.0.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "p-finally": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", + "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + } + } + }, + "jest-config": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-25.5.4.tgz", + "integrity": "sha512-SZwR91SwcdK6bz7Gco8qL7YY2sx8tFJYzvg216DLihTWf+LKY/DoJXpM9nTzYakSyfblbqeU48p/p7Jzy05Atg==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^25.5.4", + "@jest/types": "^25.5.0", + "babel-jest": "^25.5.1", + "chalk": "^3.0.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "jest-environment-jsdom": "^25.5.0", + "jest-environment-node": "^25.5.0", + "jest-get-type": "^25.2.6", + "jest-jasmine2": "^25.5.4", + "jest-regex-util": "^25.2.6", + "jest-resolve": "^25.5.1", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", + "micromatch": "^4.0.2", + "pretty-format": "^25.5.0", + "realpath-native": "^2.0.0" + } + }, + "jest-diff": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz", + "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "diff-sequences": "^25.2.6", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + } + }, + "jest-docblock": { + "version": "25.3.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-25.3.0.tgz", + "integrity": "sha512-aktF0kCar8+zxRHxQZwxMy70stc9R1mOmrLsT5VO3pIT0uzGRSDAXxSlz4NqQWpuLjPpuMhPRl7H+5FRsvIQAg==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-25.5.0.tgz", + "integrity": "sha512-QBogUxna3D8vtiItvn54xXde7+vuzqRrEeaw8r1s+1TG9eZLVJE5ZkKoSUlqFwRjnlaA4hyKGiu9OlkFIuKnjA==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "jest-get-type": "^25.2.6", + "jest-util": "^25.5.0", + "pretty-format": "^25.5.0" + } + }, + "jest-environment-jsdom": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-25.5.0.tgz", + "integrity": "sha512-7Jr02ydaq4jaWMZLY+Skn8wL5nVIYpWvmeatOHL3tOcV3Zw8sjnPpx+ZdeBfc457p8jCR9J6YCc+Lga0oIy62A==", + "dev": true, + "requires": { + "@jest/environment": "^25.5.0", + "@jest/fake-timers": "^25.5.0", + "@jest/types": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-util": "^25.5.0", + "jsdom": "^15.2.1" + } + }, + "jest-environment-node": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-25.5.0.tgz", + "integrity": "sha512-iuxK6rQR2En9EID+2k+IBs5fCFd919gVVK5BeND82fYeLWPqvRcFNPKu9+gxTwfB5XwBGBvZ0HFQa+cHtIoslA==", + "dev": true, + "requires": { + "@jest/environment": "^25.5.0", + "@jest/fake-timers": "^25.5.0", + "@jest/types": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-util": "^25.5.0", + "semver": "^6.3.0" + } + }, + "jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", + "dev": true + }, + "jest-haste-map": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-25.5.1.tgz", + "integrity": "sha512-dddgh9UZjV7SCDQUrQ+5t9yy8iEgKc1AKqZR9YDww8xsVOtzPQSMVLDChc21+g29oTRexb9/B0bIlZL+sWmvAQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "@types/graceful-fs": "^4.1.2", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.1.2", + "graceful-fs": "^4.2.4", + "jest-serializer": "^25.5.0", + "jest-util": "^25.5.0", + "jest-worker": "^25.5.0", + "micromatch": "^4.0.2", + "sane": "^4.0.3", + "walker": "^1.0.7", + "which": "^2.0.2" + } + }, + "jest-jasmine2": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-25.5.4.tgz", + "integrity": "sha512-9acbWEfbmS8UpdcfqnDO+uBUgKa/9hcRh983IHdM+pKmJPL77G0sWAAK0V0kr5LK3a8cSBfkFSoncXwQlRZfkQ==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^25.5.0", + "@jest/source-map": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "co": "^4.6.0", + "expect": "^25.5.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^25.5.0", + "jest-matcher-utils": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-runtime": "^25.5.4", + "jest-snapshot": "^25.5.1", + "jest-util": "^25.5.0", + "pretty-format": "^25.5.0", + "throat": "^5.0.0" + } + }, + "jest-leak-detector": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-25.5.0.tgz", + "integrity": "sha512-rV7JdLsanS8OkdDpZtgBf61L5xZ4NnYLBq72r6ldxahJWWczZjXawRsoHyXzibM5ed7C2QRjpp6ypgwGdKyoVA==", + "dev": true, + "requires": { + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + } + }, + "jest-matcher-utils": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-25.5.0.tgz", + "integrity": "sha512-VWI269+9JS5cpndnpCwm7dy7JtGQT30UHfrnM3mXl22gHGt/b7NkjBqXfbhZ8V4B7ANUsjK18PlSBmG0YH7gjw==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "jest-diff": "^25.5.0", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + } + }, + "jest-message-util": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-25.5.0.tgz", + "integrity": "sha512-ezddz3YCT/LT0SKAmylVyWWIGYoKHOFOFXx3/nA4m794lfVUskMcwhip6vTgdVrOtYdjeQeis2ypzes9mZb4EA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^25.5.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "slash": "^3.0.0", + "stack-utils": "^1.0.1" + } + }, + "jest-mock": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-25.5.0.tgz", + "integrity": "sha512-eXWuTV8mKzp/ovHc5+3USJMYsTBhyQ+5A1Mak35dey/RG8GlM4YWVylZuGgVXinaW6tpvk/RSecmF37FKUlpXA==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", + "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", + "dev": true + }, + "jest-regex-util": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-25.2.6.tgz", + "integrity": "sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==", + "dev": true + }, + "jest-resolve": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-25.5.1.tgz", + "integrity": "sha512-Hc09hYch5aWdtejsUZhA+vSzcotf7fajSlPA6EZPE1RmPBAD39XtJhvHWFStid58iit4IPDLI/Da4cwdDmAHiQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "browser-resolve": "^1.11.3", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.1", + "read-pkg-up": "^7.0.1", + "realpath-native": "^2.0.0", + "resolve": "^1.17.0", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-25.5.4.tgz", + "integrity": "sha512-yFmbPd+DAQjJQg88HveObcGBA32nqNZ02fjYmtL16t1xw9bAttSn5UGRRhzMHIQbsep7znWvAvnD4kDqOFM0Uw==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "jest-regex-util": "^25.2.6", + "jest-snapshot": "^25.5.1" + } + }, + "jest-runner": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-25.5.4.tgz", + "integrity": "sha512-V/2R7fKZo6blP8E9BL9vJ8aTU4TH2beuqGNxHbxi6t14XzTb+x90B3FRgdvuHm41GY8ch4xxvf0ATH4hdpjTqg==", + "dev": true, + "requires": { + "@jest/console": "^25.5.0", + "@jest/environment": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-config": "^25.5.4", + "jest-docblock": "^25.3.0", + "jest-haste-map": "^25.5.1", + "jest-jasmine2": "^25.5.4", + "jest-leak-detector": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-resolve": "^25.5.1", + "jest-runtime": "^25.5.4", + "jest-util": "^25.5.0", + "jest-worker": "^25.5.0", + "source-map-support": "^0.5.6", + "throat": "^5.0.0" + } + }, + "jest-runtime": { + "version": "25.5.4", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-25.5.4.tgz", + "integrity": "sha512-RWTt8LeWh3GvjYtASH2eezkc8AehVoWKK20udV6n3/gC87wlTbE1kIA+opCvNWyyPeBs6ptYsc6nyHUb1GlUVQ==", + "dev": true, + "requires": { + "@jest/console": "^25.5.0", + "@jest/environment": "^25.5.0", + "@jest/globals": "^25.5.2", + "@jest/source-map": "^25.5.0", + "@jest/test-result": "^25.5.0", + "@jest/transform": "^25.5.1", + "@jest/types": "^25.5.0", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-config": "^25.5.4", + "jest-haste-map": "^25.5.1", + "jest-message-util": "^25.5.0", + "jest-mock": "^25.5.0", + "jest-regex-util": "^25.2.6", + "jest-resolve": "^25.5.1", + "jest-snapshot": "^25.5.1", + "jest-util": "^25.5.0", + "jest-validate": "^25.5.0", + "realpath-native": "^2.0.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^15.3.1" + } + }, + "jest-serializer": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-25.5.0.tgz", + "integrity": "sha512-LxD8fY1lByomEPflwur9o4e2a5twSQ7TaVNLlFUuToIdoJuBt8tzHfCsZ42Ok6LkKXWzFWf3AGmheuLAA7LcCA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4" + } + }, + "jest-snapshot": { + "version": "25.5.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-25.5.1.tgz", + "integrity": "sha512-C02JE1TUe64p2v1auUJ2ze5vcuv32tkv9PyhEb318e8XOKF7MOyXdJ7kdjbvrp3ChPLU2usI7Rjxs97Dj5P0uQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^25.5.0", + "@types/prettier": "^1.19.0", + "chalk": "^3.0.0", + "expect": "^25.5.0", + "graceful-fs": "^4.2.4", + "jest-diff": "^25.5.0", + "jest-get-type": "^25.2.6", + "jest-matcher-utils": "^25.5.0", + "jest-message-util": "^25.5.0", + "jest-resolve": "^25.5.1", + "make-dir": "^3.0.0", + "natural-compare": "^1.4.0", + "pretty-format": "^25.5.0", + "semver": "^6.3.0" + } + }, + "jest-util": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-25.5.0.tgz", + "integrity": "sha512-KVlX+WWg1zUTB9ktvhsg2PXZVdkI1NBevOJSkTKYAyXyH4QSvh+Lay/e/v+bmaFfrkfx43xD8QTfgobzlEXdIA==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "chalk": "^3.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "make-dir": "^3.0.0" + } + }, + "jest-validate": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-25.5.0.tgz", + "integrity": "sha512-okUFKqhZIpo3jDdtUXUZ2LxGUZJIlfdYBvZb1aczzxrlyMlqdnnws9MOxezoLGhSaFc2XYaHNReNQfj5zPIWyQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "camelcase": "^5.3.1", + "chalk": "^3.0.0", + "jest-get-type": "^25.2.6", + "leven": "^3.1.0", + "pretty-format": "^25.5.0" + } + }, + "jest-watcher": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-25.5.0.tgz", + "integrity": "sha512-XrSfJnVASEl+5+bb51V0Q7WQx65dTSk7NL4yDdVjPnRNpM0hG+ncFmDYJo9O8jaSRcAitVbuVawyXCRoxGrT5Q==", + "dev": true, + "requires": { + "@jest/test-result": "^25.5.0", + "@jest/types": "^25.5.0", + "ansi-escapes": "^4.2.1", + "chalk": "^3.0.0", + "jest-util": "^25.5.0", + "string-length": "^3.1.0" + } + }, + "jest-worker": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-25.5.0.tgz", + "integrity": "sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsdom": { + "version": "15.2.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.1.tgz", + "integrity": "sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "acorn": "^7.1.0", + "acorn-globals": "^4.3.2", + "array-equal": "^1.0.0", + "cssom": "^0.4.1", + "cssstyle": "^2.0.0", + "data-urls": "^1.1.0", + "domexception": "^1.0.1", + "escodegen": "^1.11.1", + "html-encoding-sniffer": "^1.0.2", + "nwsapi": "^2.2.0", + "parse5": "5.1.0", + "pn": "^1.1.0", + "request": "^2.88.0", + "request-promise-native": "^1.0.7", + "saxes": "^3.1.9", + "symbol-tree": "^3.2.2", + "tough-cookie": "^3.0.1", + "w3c-hr-time": "^1.0.1", + "w3c-xmlserializer": "^1.1.2", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.5", + "whatwg-mimetype": "^2.3.0", + "whatwg-url": "^7.0.0", + "ws": "^7.0.0", + "xml-name-validator": "^3.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "json5": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "jsonpath-plus": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-0.19.0.tgz", + "integrity": "sha512-GSVwsrzW9LsA5lzsqe4CkuZ9wp+kxBb2GwNniaWzI2YFn5Ig42rSW8ZxVpWXaAfakXNrx5pgY5AbQq7kzX29kg==" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "keyv": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz", + "integrity": "sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==", + "requires": { + "json-buffer": "3.0.0" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, + "lodash.ismatch": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", + "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=" + }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, + "lolex": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", + "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", + "dev": true, + "requires": { + "tmpl": "1.0.x" + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "node-forge": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.5.tgz", + "integrity": "sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q==" + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true + }, + "node-jose": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-1.1.4.tgz", + "integrity": "sha512-L31IFwL3pWWcMHxxidCY51ezqrDXMkvlT/5pLTfNw5sXmmOLJuN6ug7txzF/iuZN55cRpyOmoJrotwBQIoo5Lw==", + "requires": { + "base64url": "^3.0.1", + "browserify-zlib": "^0.2.0", + "buffer": "^5.5.0", + "es6-promise": "^4.2.8", + "lodash": "^4.17.15", + "long": "^4.0.0", + "node-forge": "^0.8.5", + "process": "^0.11.10", + "react-zlib-js": "^1.0.4", + "uuid": "^3.3.3" + } + }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "node-notifier": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-6.0.0.tgz", + "integrity": "sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw==", + "dev": true, + "optional": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^2.1.1", + "semver": "^6.3.0", + "shellwords": "^0.1.1", + "which": "^1.3.1" + }, + "dependencies": { + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "optional": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-url": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz", + "integrity": "sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==", + "requires": { + "prepend-http": "^2.0.0", + "query-string": "^5.0.1", + "sort-keys": "^2.0.0" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-hash": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", + "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "oidc-token-hash": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-3.0.2.tgz", + "integrity": "sha512-dTzp80/y/da+um+i+sOucNqiPpwRL7M/xPwj7pH1TFA2/bqQ+OK2sJahSXbemEoLtPkHcFLyhLhLWZa9yW5+RA==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "openid-client": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-2.5.0.tgz", + "integrity": "sha512-t3hFD7xEoW1U25RyBcRFaL19fGGs6hNVTysq9pgmiltH0IVUPzH/bQV9w24pM5Q7MunnGv2/5XjIru6BQcWdxg==", + "requires": { + "base64url": "^3.0.0", + "got": "^8.3.2", + "lodash": "^4.17.11", + "lru-cache": "^5.1.1", + "node-jose": "^1.1.0", + "object-hash": "^1.3.1", + "oidc-token-hash": "^3.0.1", + "p-any": "^1.1.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "p-any": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-any/-/p-any-1.1.0.tgz", + "integrity": "sha512-Ef0tVa4CZ5pTAmKn+Cg3w8ABBXh+hHO1aV8281dKOoUHfX+3tjG2EaFcC+aZyagg9b4EYGsHEjz21DnEE8Og2g==", + "requires": { + "p-some": "^2.0.0" + } + }, + "p-cancelable": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", + "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==" + }, + "p-each-series": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.1.0.tgz", + "integrity": "sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==", + "dev": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-is-promise": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=" + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-some": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-some/-/p-some-2.0.1.tgz", + "integrity": "sha1-Zdh8ixVO289SIdFnd4ttLhUPbwY=", + "requires": { + "aggregate-error": "^1.0.0" + } + }, + "p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "requires": { + "p-finally": "^1.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "parse5": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz", + "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==", + "dev": true + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" + }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + } + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "prompts": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz", + "integrity": "sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.4" + } + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "query-string": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-5.1.1.tgz", + "integrity": "sha512-gjWOsm2SoGlgLEdAGt7a6slVOk9mGiXmPFMqrEhLQ68rhQuBnpfs3+EmlvqKyxnCo9/PPlF+9MtY02S1aFg+Jw==", + "requires": { + "decode-uri-component": "^0.2.0", + "object-assign": "^4.1.0", + "strict-uri-encode": "^1.0.0" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "react-zlib-js": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/react-zlib-js/-/react-zlib-js-1.0.4.tgz", + "integrity": "sha512-ynXD9DFxpE7vtGoa3ZwBtPmZrkZYw2plzHGbanUjBOSN4RtuXdektSfABykHtTiWEHMh7WdYj45LHtp228ZF1A==" + }, + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "dev": true, + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + }, + "dependencies": { + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", + "dev": true + } + } + }, + "read-pkg-up": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", + "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", + "dev": true, + "requires": { + "find-up": "^4.1.0", + "read-pkg": "^5.2.0", + "type-fest": "^0.8.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "realpath-native": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-2.0.0.tgz", + "integrity": "sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q==", + "dev": true + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "requires": { + "resolve": "^1.1.6" + } + }, + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "request-promise-native": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", + "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", + "dev": true, + "requires": { + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "dependencies": { + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "requires": { + "lowercase-keys": "^1.0.0" + } + }, + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true + }, + "rfc4648": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfc4648/-/rfc4648-1.3.0.tgz", + "integrity": "sha512-x36K12jOflpm1V8QjPq3I+pt7Z1xzeZIjiC8J2Oxd7bE1efTrOG241DTYVJByP/SxR9jl1t7iZqYxDX864jgBQ==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "dev": true, + "requires": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + } + } + }, + "saxes": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz", + "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==", + "dev": true, + "requires": { + "xmlchars": "^2.1.1" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "shelljs": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "sort-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", + "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", + "requires": { + "is-plain-obj": "^1.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "dev": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, + "strict-uri-encode": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz", + "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=" + }, + "string-length": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-3.1.0.tgz", + "integrity": "sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA==", + "dev": true, + "requires": { + "astral-regex": "^1.0.0", + "strip-ansi": "^5.2.0" + }, + "dependencies": { + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + } + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-hyperlinks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", + "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", + "dev": true, + "requires": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + } + }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, + "terminal-link": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", + "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "supports-hyperlinks": "^2.0.0" + } + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "dev": true + }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" + }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "dev": true, + "requires": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, + "underscore": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", + "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==" + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true + } + } + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "requires": { + "prepend-http": "^2.0.0" + } + }, + "url-to-options": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", + "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=" + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "v8-to-istanbul": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-4.1.4.tgz", + "integrity": "sha512-Rw6vJHj1mbdK8edjR7+zuJrpDtKIgNdAvTSAcpYfgMIw+u2dPDntD3dgN4XQFLU2/fvFQdzj+EeSGfd/jnY5fQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0", + "source-map": "^0.7.3" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, + "w3c-xmlserializer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", + "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", + "dev": true, + "requires": { + "domexception": "^1.0.1", + "webidl-conversions": "^4.0.2", + "xml-name-validator": "^3.0.0" + } + }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "requires": { + "makeerror": "1.0.x" + } + }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "ws": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.3.0.tgz", + "integrity": "sha512-iFtXzngZVXPGgpTlP1rBqsUK82p9tKqsWRPg5L56egiljujJT3vGAYnHANvFxBieXrTFavhzhxW52jnaWV+w2w==", + "dev": true + }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "yargs": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", + "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "dev": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.1" + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } +} diff --git a/hooks/declarative-subsequent-scans/package.json b/hooks/declarative-subsequent-scans/package.json new file mode 100644 index 00000000..a3f9d709 --- /dev/null +++ b/hooks/declarative-subsequent-scans/package.json @@ -0,0 +1,19 @@ +{ + "name": "declarative-subsequent-scans", + "version": "1.0.0", + "description": "", + "main": "hook.js", + "scripts": { + "test": "jest ." + }, + "keywords": [], + "author": "", + "license": "Apache-2.0", + "dependencies": { + "@kubernetes/client-node": "^0.12.0", + "lodash.ismatch": "^4.4.0" + }, + "devDependencies": { + "jest": "^25.1.0" + } +} diff --git a/hooks/declarative-subsequent-scans/scan-helpers.js b/hooks/declarative-subsequent-scans/scan-helpers.js new file mode 100644 index 00000000..53b22a5f --- /dev/null +++ b/hooks/declarative-subsequent-scans/scan-helpers.js @@ -0,0 +1,68 @@ +const k8s = require("@kubernetes/client-node"); + +// configure k8s client +const kc = new k8s.KubeConfig(); +kc.loadFromDefault(); + +const k8sApiCRD = kc.makeApiClient(k8s.CustomObjectsApi); + +async function startSubsequentSecureCodeBoxScan({ + parentScan, + name, + scanType, + parameters, +}) { + const scanDefinition = { + apiVersion: "execution.experimental.securecodebox.io/v1", + kind: "Scan", + metadata: { + name: name, + labels: { + ...parentScan.metadata.labels, + }, + annotations: { + 'securecodebox.io/hook': 'nmap-subsequent-scans', + 'securecodebox.io/parent-scan': parentScan.metadata.name, + }, + ...(await getOwnerReference(parentScan)), + }, + spec: { + scanType, + parameters, + }, + }; + + + try { + // Starting another subsequent sslyze scan based on the nmap results + // found at: https://github.com/kubernetes-client/javascript/blob/79736b9a608c18d818de61a6b44503a08ea3a78f/src/gen/api/customObjectsApi.ts#L209 + await k8sApiCRD.createNamespacedCustomObject( + "execution.experimental.securecodebox.io", + "v1", + "default", + "scans", + scanDefinition, + "false" + ); + } catch (error) { + console.error(`Failed to start Scan ${name}`); + console.error(error); + } +} + +async function getOwnerReference(parentScan) { + return { + ownerReferences: [ + { + apiVersion: 'execution.experimental.securecodebox.io/v1', + blockOwnerDeletion: true, + controller: true, + kind: 'Scan', + name: parentScan.metadata.name, + uid: parentScan.metadata.uid, + }, + ], + }; +} + +module.exports.startSubsequentSecureCodeBoxScan = startSubsequentSecureCodeBoxScan; diff --git a/hooks/declarative-subsequent-scans/templates/NOTES.txt b/hooks/declarative-subsequent-scans/templates/NOTES.txt new file mode 100644 index 00000000..5c320679 --- /dev/null +++ b/hooks/declarative-subsequent-scans/templates/NOTES.txt @@ -0,0 +1,2 @@ +imperative-subsequent-scans Hook deployed. +This will start possible subsequent security scans based on nmap findings (e.g. open ports found by NMAP or subdomains found by AMASS). \ No newline at end of file diff --git a/hooks/declarative-subsequent-scans/templates/_helpers.tpl b/hooks/declarative-subsequent-scans/templates/_helpers.tpl new file mode 100644 index 00000000..ec17cf6b --- /dev/null +++ b/hooks/declarative-subsequent-scans/templates/_helpers.tpl @@ -0,0 +1,52 @@ +{{/* vim: set filetype=mustache: */}} +{{/* +Expand the name of the chart. +*/}} +{{- define "declarative-subsequent-scans.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "declarative-subsequent-scans.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "declarative-subsequent-scans.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common labels +*/}} +{{- define "declarative-subsequent-scans.labels" -}} +helm.sh/chart: {{ include "declarative-subsequent-scans.chart" . }} +{{ include "declarative-subsequent-scans.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Selector labels +*/}} +{{- define "declarative-subsequent-scans.selectorLabels" -}} +app.kubernetes.io/name: {{ include "declarative-subsequent-scans.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end -}} diff --git a/hooks/declarative-subsequent-scans/templates/declerative-subsequent-scans-hook.yaml b/hooks/declarative-subsequent-scans/templates/declerative-subsequent-scans-hook.yaml new file mode 100644 index 00000000..533bc000 --- /dev/null +++ b/hooks/declarative-subsequent-scans/templates/declerative-subsequent-scans-hook.yaml @@ -0,0 +1,19 @@ +apiVersion: "execution.experimental.securecodebox.io/v1" +kind: ScanCompletionHook +metadata: + name: {{ include "declarative-subsequent-scans.fullname" . }} +spec: + type: ReadOnly + {{- if .Values.image.registry }} + {{- if .Values.image.digest }} + image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}@{{ .Values.image.digest }}" + {{- else }} + image: "{{ .Values.image.registry }}/{{ .Values.image.repository }}:{{ .Values.image.tag }}" + {{- end }} + {{- else }} + {{- if .Values.image.digest }} + image: "{{ .Values.image.repository }}@{{ .Values.image.digest }}" + {{- else }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + {{- end }} + {{- end }} \ No newline at end of file diff --git a/hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml b/hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml new file mode 100644 index 00000000..4fc2447a --- /dev/null +++ b/hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml @@ -0,0 +1,16 @@ +apiVersion: "cascading.experimental.securecodebox.io/v1" +kind: CascadingRule +metadata: + name: "tls-scans" +spec: + matches: + - category: "Open Port" + attributes: + port: 443 + service: "https" + - category: "Open Port" + attributes: + service: "https" + scanSpec: + name: "sslyze" + parameters: ["--regular", "{attributes.hostname}"] diff --git a/hooks/declarative-subsequent-scans/values.yaml b/hooks/declarative-subsequent-scans/values.yaml new file mode 100644 index 00000000..230c9bbe --- /dev/null +++ b/hooks/declarative-subsequent-scans/values.yaml @@ -0,0 +1,9 @@ +# Default values for dispatcher. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +image: + registry: docker.io + repository: scbexperimental/hook-declarative-subsequent-scans + tag: latest + digest: null From 9ebdd4cd995663f63464df813368799504d3d44b Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 10 Jun 2020 20:34:12 +0200 Subject: [PATCH 03/55] #33 Update templating syntax to mustache / handlebars --- .../declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml b/hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml index 4fc2447a..bf5372e8 100644 --- a/hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml +++ b/hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml @@ -13,4 +13,4 @@ spec: service: "https" scanSpec: name: "sslyze" - parameters: ["--regular", "{attributes.hostname}"] + parameters: ["--regular", "{{attributes.hostname}}"] From d8bc027020479a2e52087a5f61bd43b070a457d5 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 10 Jun 2020 20:34:38 +0200 Subject: [PATCH 04/55] #33 Regen deepCopy for cascading type --- .../cascading/v1/zz_generated.deepcopy.go | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/operator/apis/cascading/v1/zz_generated.deepcopy.go b/operator/apis/cascading/v1/zz_generated.deepcopy.go index 545e5df8..915199ee 100644 --- a/operator/apis/cascading/v1/zz_generated.deepcopy.go +++ b/operator/apis/cascading/v1/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ package v1 import ( runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/intstr" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -29,7 +30,7 @@ func (in *CascadingRule) DeepCopyInto(out *CascadingRule) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -86,6 +87,14 @@ func (in *CascadingRuleList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CascadingRuleSpec) DeepCopyInto(out *CascadingRuleSpec) { *out = *in + if in.Matches != nil { + in, out := &in.Matches, &out.Matches + *out = make([]MatchesRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + in.ScanSpec.DeepCopyInto(&out.ScanSpec) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CascadingRuleSpec. @@ -112,3 +121,25 @@ func (in *CascadingRuleStatus) DeepCopy() *CascadingRuleStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *MatchesRule) DeepCopyInto(out *MatchesRule) { + *out = *in + if in.Attributes != nil { + in, out := &in.Attributes, &out.Attributes + *out = make(map[string]intstr.IntOrString, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchesRule. +func (in *MatchesRule) DeepCopy() *MatchesRule { + if in == nil { + return nil + } + out := new(MatchesRule) + in.DeepCopyInto(out) + return out +} From 8a7786cff454f865f4f0dad1beb59b70905fc8c3 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 10 Jun 2020 20:35:38 +0200 Subject: [PATCH 05/55] #33 Implement Declarative CascadingScans Migrated to main hook file to typescript --- hooks/declarative-subsequent-scans/Dockerfile | 18 ++- hooks/declarative-subsequent-scans/hook.js | 153 +++++++++++++----- .../declarative-subsequent-scans/hook.test.js | 4 +- hooks/declarative-subsequent-scans/hook.ts | 94 +++++++++++ .../package-lock.json | 16 +- .../declarative-subsequent-scans/package.json | 7 +- .../scan-helpers.js | 56 ++++--- 7 files changed, 276 insertions(+), 72 deletions(-) create mode 100644 hooks/declarative-subsequent-scans/hook.ts diff --git a/hooks/declarative-subsequent-scans/Dockerfile b/hooks/declarative-subsequent-scans/Dockerfile index 880f259c..c9b94719 100644 --- a/hooks/declarative-subsequent-scans/Dockerfile +++ b/hooks/declarative-subsequent-scans/Dockerfile @@ -1,5 +1,19 @@ -# This image doesn't install the hooks dependencies, as it only has the @kubernetes/client-node dependencies which is already installed via the hook-sdk +FROM node:12-alpine as install +RUN mkdir -p /home/app +WORKDIR /home/app +COPY package.json package-lock.json ./ +RUN npm ci --production + +FROM node:12-alpine as build +RUN mkdir -p /home/app +WORKDIR /home/app +COPY package.json package-lock.json ./ +RUN npm ci +COPY hook.ts scan-helpers.js ./ +RUN npm run build FROM scbexperimental/hook-sdk-nodejs:latest WORKDIR /home/app/hook-wrapper/hook/ -COPY --chown=app:app hook.js scan-helpers.js ./ +COPY --chown=app:app scan-helpers.js ./ +COPY --from=build --chown=app:app /home/app/hook.js ./ +COPY --from=install --chown=app:app /home/app/node_modules/ ./node_modules/ diff --git a/hooks/declarative-subsequent-scans/hook.js b/hooks/declarative-subsequent-scans/hook.js index 0eac9afe..a8c6a7f9 100644 --- a/hooks/declarative-subsequent-scans/hook.js +++ b/hooks/declarative-subsequent-scans/hook.js @@ -1,46 +1,119 @@ -const { startSubsequentSecureCodeBoxScan } = require("./scan-helpers"); -const isMatch = require("lodash.ismatch"); - -async function handle({ scan, getFindings }) { - const findings = await getFindings(); - const cascadingRules = await getCascadingRules(); - - const cascadingScans = getCascadingScans(findings, cascadingRules); - - for (const { scanType, parameters } of cascadingScans) { - await startSubsequentSecureCodeBoxScan({ - parentScan: scan, - scanType, - parameters, +"use strict"; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +exports.__esModule = true; +exports.getCascadingScans = exports.handle = void 0; +var lodash_1 = require("lodash"); +var Mustache = require("mustache"); +var scan_helpers_1 = require("./scan-helpers"); +function handle(_a) { + var scan = _a.scan, getFindings = _a.getFindings; + return __awaiter(this, void 0, void 0, function () { + var findings, cascadingRules, cascadingScans, _i, cascadingScans_1, _b, name_1, parameters; + return __generator(this, function (_c) { + switch (_c.label) { + case 0: return [4 /*yield*/, getFindings()]; + case 1: + findings = _c.sent(); + return [4 /*yield*/, getCascadingRules()]; + case 2: + cascadingRules = _c.sent(); + cascadingScans = getCascadingScans(findings, cascadingRules); + _i = 0, cascadingScans_1 = cascadingScans; + _c.label = 3; + case 3: + if (!(_i < cascadingScans_1.length)) return [3 /*break*/, 6]; + _b = cascadingScans_1[_i], name_1 = _b.name, parameters = _b.parameters; + return [4 /*yield*/, scan_helpers_1.startSubsequentSecureCodeBoxScan({ + parentScan: scan, + scanType: name_1, + parameters: parameters + })]; + case 4: + _c.sent(); + _c.label = 5; + case 5: + _i++; + return [3 /*break*/, 3]; + case 6: return [2 /*return*/]; + } + }); }); - } } - -async function getCascadingRules() { - // Todo: Get all CascadingRules of the current Namespace via k8s api - return []; +exports.handle = handle; +function getCascadingRules() { + return __awaiter(this, void 0, void 0, function () { + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, scan_helpers_1.getCascadingRulesFromCluster()]; + case 1: + // Explicit Cast to the proper Type + return [2 /*return*/, _a.sent()]; + } + }); + }); } - -// Todo remove eslint disable -// eslint-disable-next-line no-unused-vars +/** + * Goes thought the Findings and the CascadingRules + * and returns a List of Scans which should be started based on both. + */ function getCascadingScans(findings, cascadingRules) { - const cascadingScans = []; - - for (const cascadingRule of cascadingRules) { - for (const finding of findings) { - const matches = cascadingRule.spec.matches.some((matchesRule) => - isMatch(finding, matchesRule) - ); - - if (matches) { - // Todo templating - cascadingScans.push(cascadingRule.spec.scanSpec); - } + var cascadingScans = []; + for (var _i = 0, cascadingRules_1 = cascadingRules; _i < cascadingRules_1.length; _i++) { + var cascadingRule = cascadingRules_1[_i]; + var _loop_1 = function (finding) { + var matches = cascadingRule.spec.matches.some(function (matchesRule) { + return lodash_1.isMatch(finding, matchesRule); + }); + if (matches) { + var _a = cascadingRule.spec.scanSpec, name_2 = _a.name, parameters = _a.parameters; + cascadingScans.push({ + name: Mustache.render(name_2, finding), + parameters: parameters.map(function (parameter) { + return Mustache.render(parameter, finding); + }) + }); + } + }; + for (var _a = 0, findings_1 = findings; _a < findings_1.length; _a++) { + var finding = findings_1[_a]; + _loop_1(finding); + } } - } - - return cascadingScans; + return cascadingScans; } - -module.exports.getCascadingScans = getCascadingScans; -module.exports.handle = handle; +exports.getCascadingScans = getCascadingScans; diff --git a/hooks/declarative-subsequent-scans/hook.test.js b/hooks/declarative-subsequent-scans/hook.test.js index 27861950..4a580464 100644 --- a/hooks/declarative-subsequent-scans/hook.test.js +++ b/hooks/declarative-subsequent-scans/hook.test.js @@ -39,7 +39,7 @@ test("Should create subsequent scans for open HTTPS ports (NMAP findings)", () = ], scanSpec: { name: "sslyze", - parameters: ["--regular", "{attributes.hostname}"] + parameters: ["--regular", "{{attributes.hostname}}"] } } } @@ -53,7 +53,7 @@ test("Should create subsequent scans for open HTTPS ports (NMAP findings)", () = "name": "sslyze", "parameters": Array [ "--regular", - "{attributes.hostname}", + "foobar.com", ], }, ] diff --git a/hooks/declarative-subsequent-scans/hook.ts b/hooks/declarative-subsequent-scans/hook.ts new file mode 100644 index 00000000..69b28f41 --- /dev/null +++ b/hooks/declarative-subsequent-scans/hook.ts @@ -0,0 +1,94 @@ +import { isMatch } from "lodash"; +import * as Mustache from "mustache"; +import * as k8s from "@kubernetes/client-node"; + +import { + startSubsequentSecureCodeBoxScan, + getCascadingRulesFromCluster, +} from "./scan-helpers"; + +interface Finding { + name: string; + location: string; + category: string; + severity: string; + osi_layer: string; + attributes: Map; +} + +interface HandleArgs { + scan: any; + getFindings: () => Array; +} + +interface CascadingRules { + metadata: k8s.V1ObjectMeta; + spec: CascadingRuleSpec; +} + +interface CascadingRuleSpec { + matches: Array; + scanSpec: ScanSpec; +} + +interface Scan { + metadata: k8s.V1ObjectMeta; + spec: ScanSpec; +} + +interface ScanSpec { + name: string; + parameters: Array; +} + +export async function handle({ scan, getFindings }: HandleArgs) { + const findings = await getFindings(); + const cascadingRules = await getCascadingRules(); + + const cascadingScans = getCascadingScans(findings, cascadingRules); + + for (const { name, parameters } of cascadingScans) { + await startSubsequentSecureCodeBoxScan({ + parentScan: scan, + scanType: name, + parameters, + }); + } +} + +async function getCascadingRules(): Promise> { + // Explicit Cast to the proper Type + return >await getCascadingRulesFromCluster(); +} + +/** + * Goes thought the Findings and the CascadingRules + * and returns a List of Scans which should be started based on both. + */ +export function getCascadingScans( + findings: Array, + cascadingRules: Array +): Array { + const cascadingScans: Array = []; + + for (const cascadingRule of cascadingRules) { + for (const finding of findings) { + const matches = cascadingRule.spec.matches.some((matchesRule) => + isMatch(finding, matchesRule) + ); + + if (matches) { + const { name, parameters } = cascadingRule.spec.scanSpec; + + cascadingScans.push({ + name: Mustache.render(name, finding), + parameters: parameters.map((parameter) => + Mustache.render(parameter, finding) + ), + }); + } + } + } + + return cascadingScans; +} diff --git a/hooks/declarative-subsequent-scans/package-lock.json b/hooks/declarative-subsequent-scans/package-lock.json index bb4ce66d..016c2ff4 100644 --- a/hooks/declarative-subsequent-scans/package-lock.json +++ b/hooks/declarative-subsequent-scans/package-lock.json @@ -3199,11 +3199,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, - "lodash.ismatch": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.ismatch/-/lodash.ismatch-4.4.0.tgz", - "integrity": "sha1-dWy1FQyjum8RCFp4hJZF8Yj4Xzc=" - }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", @@ -3351,6 +3346,11 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "mustache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.0.1.tgz", + "integrity": "sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA==" + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -4873,6 +4873,12 @@ "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "3.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.5.tgz", + "integrity": "sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ==", + "dev": true + }, "underscore": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", diff --git a/hooks/declarative-subsequent-scans/package.json b/hooks/declarative-subsequent-scans/package.json index a3f9d709..8efb5c31 100644 --- a/hooks/declarative-subsequent-scans/package.json +++ b/hooks/declarative-subsequent-scans/package.json @@ -4,6 +4,7 @@ "description": "", "main": "hook.js", "scripts": { + "build": "npx typescript hook.ts", "test": "jest ." }, "keywords": [], @@ -11,9 +12,11 @@ "license": "Apache-2.0", "dependencies": { "@kubernetes/client-node": "^0.12.0", - "lodash.ismatch": "^4.4.0" + "lodash": "^4.17.15", + "mustache": "^4.0.1" }, "devDependencies": { - "jest": "^25.1.0" + "jest": "^25.1.0", + "typescript": "^3.9.5" } } diff --git a/hooks/declarative-subsequent-scans/scan-helpers.js b/hooks/declarative-subsequent-scans/scan-helpers.js index 53b22a5f..8a150ada 100644 --- a/hooks/declarative-subsequent-scans/scan-helpers.js +++ b/hooks/declarative-subsequent-scans/scan-helpers.js @@ -8,23 +8,32 @@ const k8sApiCRD = kc.makeApiClient(k8s.CustomObjectsApi); async function startSubsequentSecureCodeBoxScan({ parentScan, - name, scanType, parameters, }) { + const name = `${parentScan.metadata.name}-${scanType}`; const scanDefinition = { apiVersion: "execution.experimental.securecodebox.io/v1", kind: "Scan", metadata: { - name: name, + name, labels: { ...parentScan.metadata.labels, }, annotations: { - 'securecodebox.io/hook': 'nmap-subsequent-scans', - 'securecodebox.io/parent-scan': parentScan.metadata.name, + "securecodebox.io/hook": "nmap-subsequent-scans", + "securecodebox.io/parent-scan": parentScan.metadata.name, }, - ...(await getOwnerReference(parentScan)), + ownerReferences: [ + { + apiVersion: "execution.experimental.securecodebox.io/v1", + blockOwnerDeletion: true, + controller: true, + kind: "Scan", + name: parentScan.metadata.name, + uid: parentScan.metadata.uid, + }, + ], }, spec: { scanType, @@ -32,7 +41,6 @@ async function startSubsequentSecureCodeBoxScan({ }, }; - try { // Starting another subsequent sslyze scan based on the nmap results // found at: https://github.com/kubernetes-client/javascript/blob/79736b9a608c18d818de61a6b44503a08ea3a78f/src/gen/api/customObjectsApi.ts#L209 @@ -50,19 +58,25 @@ async function startSubsequentSecureCodeBoxScan({ } } -async function getOwnerReference(parentScan) { - return { - ownerReferences: [ - { - apiVersion: 'execution.experimental.securecodebox.io/v1', - blockOwnerDeletion: true, - controller: true, - kind: 'Scan', - name: parentScan.metadata.name, - uid: parentScan.metadata.uid, - }, - ], - }; -} - module.exports.startSubsequentSecureCodeBoxScan = startSubsequentSecureCodeBoxScan; + +async function getCascadingRulesFromCluster() { + try { + const namespace = process.env["NAMESPACE"]; + const { body } = await k8sApiCRD.listNamespacedCustomObject( + "cascading.experimental.securecodebox.io", + "v1", + namespace, + "cascadingrules" + ); + console.log("got CascadingRules"); + console.log(body); + console.log(JSON.stringify(body)); + return body.items; + } catch (err) { + console.error("Failed to get CascadingRules from the kubernetes api"); + console.error(err); + process.exit(1); + } +} +module.exports.getCascadingRulesFromCluster = getCascadingRulesFromCluster; From 79ca76f8c590653134155ad20636a1da96bd983a Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 10 Jun 2020 20:44:53 +0200 Subject: [PATCH 06/55] #33 Add ci build for declarative scan hook --- .github/workflows/ci.yaml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7a5989c3..b7188d2b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -248,6 +248,17 @@ jobs: path: ./hooks/imperative-subsequent-scans/ tag_with_ref: true build_args: baseImageTag=ci-local + - uses: docker/build-push-action@v1 + name: "Build & Push UpdateField Hook Image" + name: "Build & Push DeclarativeSubsequentScans Hook Image" + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + repository: scbexperimental/hook-declarative-subsequent-scans + path: ./hooks/declarative-subsequent-scans/ + tag_with_ref: true + tag_with_sha: true + build_args: baseImageTag=ci-local - uses: docker/build-push-action@v1 name: "Build & Push UpdateField Hook Image" with: From dde929c704d387c6ee6e7b4a2c42599db333cc02 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 10 Jun 2020 20:45:57 +0200 Subject: [PATCH 07/55] #33 Remove compiled artifact --- hooks/declarative-subsequent-scans/.gitignore | 3 +- hooks/declarative-subsequent-scans/hook.js | 119 ------------------ 2 files changed, 2 insertions(+), 120 deletions(-) delete mode 100644 hooks/declarative-subsequent-scans/hook.js diff --git a/hooks/declarative-subsequent-scans/.gitignore b/hooks/declarative-subsequent-scans/.gitignore index b512c09d..bea48df4 100644 --- a/hooks/declarative-subsequent-scans/.gitignore +++ b/hooks/declarative-subsequent-scans/.gitignore @@ -1 +1,2 @@ -node_modules \ No newline at end of file +node_modules +hook.js \ No newline at end of file diff --git a/hooks/declarative-subsequent-scans/hook.js b/hooks/declarative-subsequent-scans/hook.js deleted file mode 100644 index a8c6a7f9..00000000 --- a/hooks/declarative-subsequent-scans/hook.js +++ /dev/null @@ -1,119 +0,0 @@ -"use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -var __generator = (this && this.__generator) || function (thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -}; -exports.__esModule = true; -exports.getCascadingScans = exports.handle = void 0; -var lodash_1 = require("lodash"); -var Mustache = require("mustache"); -var scan_helpers_1 = require("./scan-helpers"); -function handle(_a) { - var scan = _a.scan, getFindings = _a.getFindings; - return __awaiter(this, void 0, void 0, function () { - var findings, cascadingRules, cascadingScans, _i, cascadingScans_1, _b, name_1, parameters; - return __generator(this, function (_c) { - switch (_c.label) { - case 0: return [4 /*yield*/, getFindings()]; - case 1: - findings = _c.sent(); - return [4 /*yield*/, getCascadingRules()]; - case 2: - cascadingRules = _c.sent(); - cascadingScans = getCascadingScans(findings, cascadingRules); - _i = 0, cascadingScans_1 = cascadingScans; - _c.label = 3; - case 3: - if (!(_i < cascadingScans_1.length)) return [3 /*break*/, 6]; - _b = cascadingScans_1[_i], name_1 = _b.name, parameters = _b.parameters; - return [4 /*yield*/, scan_helpers_1.startSubsequentSecureCodeBoxScan({ - parentScan: scan, - scanType: name_1, - parameters: parameters - })]; - case 4: - _c.sent(); - _c.label = 5; - case 5: - _i++; - return [3 /*break*/, 3]; - case 6: return [2 /*return*/]; - } - }); - }); -} -exports.handle = handle; -function getCascadingRules() { - return __awaiter(this, void 0, void 0, function () { - return __generator(this, function (_a) { - switch (_a.label) { - case 0: return [4 /*yield*/, scan_helpers_1.getCascadingRulesFromCluster()]; - case 1: - // Explicit Cast to the proper Type - return [2 /*return*/, _a.sent()]; - } - }); - }); -} -/** - * Goes thought the Findings and the CascadingRules - * and returns a List of Scans which should be started based on both. - */ -function getCascadingScans(findings, cascadingRules) { - var cascadingScans = []; - for (var _i = 0, cascadingRules_1 = cascadingRules; _i < cascadingRules_1.length; _i++) { - var cascadingRule = cascadingRules_1[_i]; - var _loop_1 = function (finding) { - var matches = cascadingRule.spec.matches.some(function (matchesRule) { - return lodash_1.isMatch(finding, matchesRule); - }); - if (matches) { - var _a = cascadingRule.spec.scanSpec, name_2 = _a.name, parameters = _a.parameters; - cascadingScans.push({ - name: Mustache.render(name_2, finding), - parameters: parameters.map(function (parameter) { - return Mustache.render(parameter, finding); - }) - }); - } - }; - for (var _a = 0, findings_1 = findings; _a < findings_1.length; _a++) { - var finding = findings_1[_a]; - _loop_1(finding); - } - } - return cascadingScans; -} -exports.getCascadingScans = getCascadingScans; From b10f7e899598120839d88c9109463910b4c272e2 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 10 Jun 2020 20:52:37 +0200 Subject: [PATCH 08/55] #33 Compile typescript before executing the tests --- .github/workflows/ci.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b7188d2b..41649356 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -40,6 +40,10 @@ jobs: cd - cd hooks/ npm ci + - name: "Compile Typescript" + run: | + cd hooks/declarative-subsequent-scans + npm run build - name: "Run tests & publish code coverage" uses: paambaati/codeclimate-action@v2.6.0 env: From 2a5f537785fd6f0f92d01135b2d7c69a1ee2f5a9 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 10 Jun 2020 20:52:49 +0200 Subject: [PATCH 09/55] #33 Ignore compiled typescript file in eslint --- .eslintignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index d70b3d1b..48d620b0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ **/node_modules -**/coverage \ No newline at end of file +**/coverage +hooks/declarative-subsequent-scans/hook.js \ No newline at end of file From a080c8f605c4e8d8094e2b33041baff78b6b539a Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 10 Jun 2020 20:58:38 +0200 Subject: [PATCH 10/55] #33 Generate source map for better code coverage tracking --- hooks/declarative-subsequent-scans/.gitignore | 3 ++- hooks/declarative-subsequent-scans/package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/hooks/declarative-subsequent-scans/.gitignore b/hooks/declarative-subsequent-scans/.gitignore index bea48df4..4e7a3b8e 100644 --- a/hooks/declarative-subsequent-scans/.gitignore +++ b/hooks/declarative-subsequent-scans/.gitignore @@ -1,2 +1,3 @@ node_modules -hook.js \ No newline at end of file +hook.js +hook.js.map \ No newline at end of file diff --git a/hooks/declarative-subsequent-scans/package.json b/hooks/declarative-subsequent-scans/package.json index 8efb5c31..e301ccf6 100644 --- a/hooks/declarative-subsequent-scans/package.json +++ b/hooks/declarative-subsequent-scans/package.json @@ -4,7 +4,7 @@ "description": "", "main": "hook.js", "scripts": { - "build": "npx typescript hook.ts", + "build": "npx typescript hook.ts --sourceMap", "test": "jest ." }, "keywords": [], From b2b9796c2ac51bbd56b4ca6c391c4d01ee101c43 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Tue, 16 Jun 2020 12:50:42 +0200 Subject: [PATCH 11/55] #33 Minor refactor --- hooks/declarative-subsequent-scans/hook.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/hooks/declarative-subsequent-scans/hook.ts b/hooks/declarative-subsequent-scans/hook.ts index 69b28f41..ccf2259d 100644 --- a/hooks/declarative-subsequent-scans/hook.ts +++ b/hooks/declarative-subsequent-scans/hook.ts @@ -16,11 +16,6 @@ interface Finding { attributes: Map; } -interface HandleArgs { - scan: any; - getFindings: () => Array; -} - interface CascadingRules { metadata: k8s.V1ObjectMeta; spec: CascadingRuleSpec; @@ -41,6 +36,11 @@ interface ScanSpec { parameters: Array; } +interface HandleArgs { + scan: Scan; + getFindings: () => Array; +} + export async function handle({ scan, getFindings }: HandleArgs) { const findings = await getFindings(); const cascadingRules = await getCascadingRules(); @@ -73,6 +73,7 @@ export function getCascadingScans( for (const cascadingRule of cascadingRules) { for (const finding of findings) { + // Check if one (ore more) of the CascadingRule matchers apply to the finding const matches = cascadingRule.spec.matches.some((matchesRule) => isMatch(finding, matchesRule) ); From ffb03e2384f86735d1e6ce71c28020cb13ebe69c Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 22 Jun 2020 11:04:25 +0200 Subject: [PATCH 12/55] #33 Introduce a operator which indicates how multiple matchers are combined --- hooks/declarative-subsequent-scans/hook.ts | 8 ++- .../tmpCascadingRules/sslyze.yaml | 15 ++--- .../apis/cascading/v1/cascadingrule_types.go | 10 +++- .../cascading/v1/zz_generated.deepcopy.go | 30 +++++++--- ...ental.securecodebox.io_cascadingrules.yaml | 58 ++++++++++--------- 5 files changed, 76 insertions(+), 45 deletions(-) diff --git a/hooks/declarative-subsequent-scans/hook.ts b/hooks/declarative-subsequent-scans/hook.ts index ccf2259d..24f5c3e9 100644 --- a/hooks/declarative-subsequent-scans/hook.ts +++ b/hooks/declarative-subsequent-scans/hook.ts @@ -22,10 +22,14 @@ interface CascadingRules { } interface CascadingRuleSpec { - matches: Array; + matches: Matches; scanSpec: ScanSpec; } +interface Matches { + anyOf: Array; +} + interface Scan { metadata: k8s.V1ObjectMeta; spec: ScanSpec; @@ -74,7 +78,7 @@ export function getCascadingScans( for (const cascadingRule of cascadingRules) { for (const finding of findings) { // Check if one (ore more) of the CascadingRule matchers apply to the finding - const matches = cascadingRule.spec.matches.some((matchesRule) => + const matches = cascadingRule.spec.matches.anyOf.some((matchesRule) => isMatch(finding, matchesRule) ); diff --git a/hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml b/hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml index bf5372e8..5df2cea8 100644 --- a/hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml +++ b/hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml @@ -4,13 +4,14 @@ metadata: name: "tls-scans" spec: matches: - - category: "Open Port" - attributes: - port: 443 - service: "https" - - category: "Open Port" - attributes: - service: "https" + anyOf: + - category: "Open Port" + attributes: + port: 443 + service: "https" + - category: "Open Port" + attributes: + service: "https" scanSpec: name: "sslyze" parameters: ["--regular", "{{attributes.hostname}}"] diff --git a/operator/apis/cascading/v1/cascadingrule_types.go b/operator/apis/cascading/v1/cascadingrule_types.go index dcc9f76c..89e91219 100644 --- a/operator/apis/cascading/v1/cascadingrule_types.go +++ b/operator/apis/cascading/v1/cascadingrule_types.go @@ -30,11 +30,17 @@ type CascadingRuleSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - // Foo is an example field of CascadingRule. Edit CascadingRule_types.go to remove/update - Matches []MatchesRule `json:"matches"` + // Matches defines to which findings the CascadingRule should apply + Matches Matches `json:"matches"` + // ScanSpec defines how the cascaded scan should look like ScanSpec executionv1.ScanSpec `json:"scanSpec"` } +// Matches defines how matching rules should be combined. Do all have to match? Or just One? +type Matches struct { + AnyOf []MatchesRule `json:"anyOf,omitempty"` +} + // MatchesRule is a generic map which is used to model the structure of a finding for which the CascadingRule should take effect type MatchesRule struct { Name string `json:"name,omitempty"` diff --git a/operator/apis/cascading/v1/zz_generated.deepcopy.go b/operator/apis/cascading/v1/zz_generated.deepcopy.go index 915199ee..f5f049ed 100644 --- a/operator/apis/cascading/v1/zz_generated.deepcopy.go +++ b/operator/apis/cascading/v1/zz_generated.deepcopy.go @@ -87,13 +87,7 @@ func (in *CascadingRuleList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CascadingRuleSpec) DeepCopyInto(out *CascadingRuleSpec) { *out = *in - if in.Matches != nil { - in, out := &in.Matches, &out.Matches - *out = make([]MatchesRule, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } + in.Matches.DeepCopyInto(&out.Matches) in.ScanSpec.DeepCopyInto(&out.ScanSpec) } @@ -122,6 +116,28 @@ func (in *CascadingRuleStatus) DeepCopy() *CascadingRuleStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Matches) DeepCopyInto(out *Matches) { + *out = *in + if in.AnyOf != nil { + in, out := &in.AnyOf, &out.AnyOf + *out = make([]MatchesRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Matches. +func (in *Matches) DeepCopy() *Matches { + if in == nil { + return nil + } + out := new(Matches) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MatchesRule) DeepCopyInto(out *MatchesRule) { *out = *in diff --git a/operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml b/operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml index a34d53e3..f62a0ea3 100644 --- a/operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml +++ b/operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml @@ -35,35 +35,39 @@ spec: description: CascadingRuleSpec defines the desired state of CascadingRule properties: matches: - description: Foo is an example field of CascadingRule. Edit CascadingRule_types.go - to remove/update - items: - description: MatchesRule is a generic map which is used to model the - structure of a finding for which the CascadingRule should take effect - properties: - attributes: - additionalProperties: - anyOf: - - type: integer - - type: string - x-kubernetes-int-or-string: true + description: Matches defines to which findings the CascadingRule should + apply + properties: + anyOf: + items: + description: MatchesRule is a generic map which is used to model + the structure of a finding for which the CascadingRule should + take effect + properties: + attributes: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + category: + type: string + description: + type: string + location: + type: string + name: + type: string + osi_layer: + type: string + severity: + type: string type: object - category: - type: string - description: - type: string - location: - type: string - name: - type: string - osi_layer: - type: string - severity: - type: string - type: object - type: array + type: array + type: object scanSpec: - description: ScanSpec defines the desired state of Scan + description: ScanSpec defines how the cascaded scan should look like properties: parameters: items: From c4f0c5746bfaa6b87dc399b83e1e96498f83e740 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 22 Jun 2020 11:04:41 +0200 Subject: [PATCH 13/55] #33 Update / Fix post install notes --- .../templates/NOTES.txt | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/hooks/declarative-subsequent-scans/templates/NOTES.txt b/hooks/declarative-subsequent-scans/templates/NOTES.txt index 5c320679..011bcef9 100644 --- a/hooks/declarative-subsequent-scans/templates/NOTES.txt +++ b/hooks/declarative-subsequent-scans/templates/NOTES.txt @@ -1,2 +1,13 @@ -imperative-subsequent-scans Hook deployed. -This will start possible subsequent security scans based on nmap findings (e.g. open ports found by NMAP or subdomains found by AMASS). \ No newline at end of file +Declarative Subsequent Scan Hook deployed. + +This will allow you to start Scans based on previous findings. +E.g. start a ssh scan for every open ssh port found. + +The rules are defined as kubernetes CRD's. You can list all of these `CascadingScanRules` by running: + +$ kubectl get cascadingrules + +You need to explicitly turn on scan cascading for every scan you use. +You can do that by setting a label selector which matches all rules you want to use. + +Find out more, on the docs: TODO \ No newline at end of file From 2e2684fc586e305a195150144adbf793cd7fd6dd Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 22 Jun 2020 14:05:16 +0200 Subject: [PATCH 14/55] #33 Enable to override the base image --- hooks/declarative-subsequent-scans/Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hooks/declarative-subsequent-scans/Dockerfile b/hooks/declarative-subsequent-scans/Dockerfile index c9b94719..bff2f4c3 100644 --- a/hooks/declarative-subsequent-scans/Dockerfile +++ b/hooks/declarative-subsequent-scans/Dockerfile @@ -1,3 +1,4 @@ +ARG baseImageTag FROM node:12-alpine as install RUN mkdir -p /home/app WORKDIR /home/app @@ -12,7 +13,7 @@ RUN npm ci COPY hook.ts scan-helpers.js ./ RUN npm run build -FROM scbexperimental/hook-sdk-nodejs:latest +FROM scbexperimental/hook-sdk-nodejs:${baseImageTag:-latest} WORKDIR /home/app/hook-wrapper/hook/ COPY --chown=app:app scan-helpers.js ./ COPY --from=build --chown=app:app /home/app/hook.js ./ From fc2b78dc60d055128d35e97553bafd9b52da2a8e Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 22 Jun 2020 14:32:22 +0200 Subject: [PATCH 15/55] #33 #21 Truncate ending dots This would otherwise lead to invalid names for k8s objects --- operator/utils/truncatedname.go | 14 ++++++++++++-- operator/utils/truncatedname_test.go | 10 ++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/operator/utils/truncatedname.go b/operator/utils/truncatedname.go index 21e215a9..b6e85e9f 100644 --- a/operator/utils/truncatedname.go +++ b/operator/utils/truncatedname.go @@ -1,11 +1,21 @@ package utils -import "fmt" +import ( + "fmt" + "strings" +) // TruncateName Ensures that the name used for a kubernetes object doesn't exceed the 63 char length limit. This actually cuts of anything after char 57, so that we can use the randomly generated suffix from k8s `generateName`. func TruncateName(name string) string { if len(name) >= 57 { - return fmt.Sprintf("%s-", name[0:57]) + name = name[0:57] } + + // Ensure that the string does not end in a dot. + // This would not be a valid domain name thous rejected by kubernetes + if strings.HasSuffix(name, ".") { + name = name[0:(len(name) - 1)] + } + return fmt.Sprintf("%s-", name) } diff --git a/operator/utils/truncatedname_test.go b/operator/utils/truncatedname_test.go index a0e68b70..973d63e2 100644 --- a/operator/utils/truncatedname_test.go +++ b/operator/utils/truncatedname_test.go @@ -24,6 +24,16 @@ func TestAbc(t *testing.T) { in: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", out: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-", }, + // Truncates strings ending in dots + { + in: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.", + out: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-", + }, + // Also removes dots even when they are not the last char + { + in: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.a", + out: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-", + }, } for _, test := range tests { From 91082810930c0530f8fbd811e52ef71c3c4b9890 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 22 Jun 2020 16:21:50 +0200 Subject: [PATCH 16/55] #33 Add CascadingRules to the sslyze scanner --- .../sslyze/cascading-rules/https.yaml | 7 ++- scanners/sslyze/cascading-rules/mail.yaml | 50 +++++++++++++++++++ .../sslyze/templates/cascading-rules.yaml | 8 +++ 3 files changed, 61 insertions(+), 4 deletions(-) rename hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml => scanners/sslyze/cascading-rules/https.yaml (68%) create mode 100644 scanners/sslyze/cascading-rules/mail.yaml create mode 100644 scanners/sslyze/templates/cascading-rules.yaml diff --git a/hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml b/scanners/sslyze/cascading-rules/https.yaml similarity index 68% rename from hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml rename to scanners/sslyze/cascading-rules/https.yaml index 5df2cea8..bf67b70f 100644 --- a/hooks/declarative-subsequent-scans/tmpCascadingRules/sslyze.yaml +++ b/scanners/sslyze/cascading-rules/https.yaml @@ -1,17 +1,16 @@ apiVersion: "cascading.experimental.securecodebox.io/v1" kind: CascadingRule metadata: - name: "tls-scans" + name: "https-tls-scans" spec: matches: anyOf: - category: "Open Port" attributes: port: 443 - service: "https" - category: "Open Port" attributes: service: "https" scanSpec: - name: "sslyze" - parameters: ["--regular", "{{attributes.hostname}}"] + scanType: "sslyze" + parameters: ["--regular", "{{attributes.hostname}}:{{attributes.port}}"] diff --git a/scanners/sslyze/cascading-rules/mail.yaml b/scanners/sslyze/cascading-rules/mail.yaml new file mode 100644 index 00000000..281d3df5 --- /dev/null +++ b/scanners/sslyze/cascading-rules/mail.yaml @@ -0,0 +1,50 @@ +apiVersion: "cascading.experimental.securecodebox.io/v1" +kind: CascadingRule +metadata: + name: "smtps-tls-scans" +spec: + matches: + anyOf: + - category: "Open Port" + attributes: + port: 465 + - category: "Open Port" + attributes: + service: "smtps" + scanSpec: + scanType: "sslyze" + parameters: ["--regular", "{{attributes.hostname}}:{{attributes.port}}"] +--- +apiVersion: "cascading.experimental.securecodebox.io/v1" +kind: CascadingRule +metadata: + name: "pop3s-tls-scans" +spec: + matches: + anyOf: + - category: "Open Port" + attributes: + port: 995 + - category: "Open Port" + attributes: + service: "pop3s" + scanSpec: + scanType: "sslyze" + parameters: ["--regular", "{{attributes.hostname}}:{{attributes.port}}"] +--- +apiVersion: "cascading.experimental.securecodebox.io/v1" +kind: CascadingRule +metadata: + name: "imaps-tls-scans" +spec: + matches: + anyOf: + - category: "Open Port" + attributes: + port: 993 + - category: "Open Port" + attributes: + service: "imaps" + scanSpec: + scanType: "sslyze" + parameters: ["--regular", "{{attributes.hostname}}:{{attributes.port}}"] diff --git a/scanners/sslyze/templates/cascading-rules.yaml b/scanners/sslyze/templates/cascading-rules.yaml new file mode 100644 index 00000000..ce556e7d --- /dev/null +++ b/scanners/sslyze/templates/cascading-rules.yaml @@ -0,0 +1,8 @@ +# The CascadingRules are not directly in the /templates directory as their curly bracket syntax clashes with helms templates ... :( +# We import them as raw files to avoid these clashes as escaping them is even more messy +{{ range $path, $_ := .Files.Glob "cascading-rules/*" }} +# Include File +{{ $.Files.Get $path }} +# Separate multiple files +--- +{{ end }} \ No newline at end of file From 80c4dc62598761b63a4042efdd29b1ea28186a81 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 22 Jun 2020 16:23:29 +0200 Subject: [PATCH 17/55] #33 Fix wrong attribute `name` to `scanType` --- hooks/declarative-subsequent-scans/hook.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/hooks/declarative-subsequent-scans/hook.ts b/hooks/declarative-subsequent-scans/hook.ts index 24f5c3e9..565ec7eb 100644 --- a/hooks/declarative-subsequent-scans/hook.ts +++ b/hooks/declarative-subsequent-scans/hook.ts @@ -36,7 +36,7 @@ interface Scan { } interface ScanSpec { - name: string; + scanType: string; parameters: Array; } @@ -51,10 +51,10 @@ export async function handle({ scan, getFindings }: HandleArgs) { const cascadingScans = getCascadingScans(findings, cascadingRules); - for (const { name, parameters } of cascadingScans) { + for (const { scanType, parameters } of cascadingScans) { await startSubsequentSecureCodeBoxScan({ parentScan: scan, - scanType: name, + scanType, parameters, }); } @@ -83,10 +83,10 @@ export function getCascadingScans( ); if (matches) { - const { name, parameters } = cascadingRule.spec.scanSpec; + const { scanType, parameters } = cascadingRule.spec.scanSpec; cascadingScans.push({ - name: Mustache.render(name, finding), + scanType: Mustache.render(scanType, finding), parameters: parameters.map((parameter) => Mustache.render(parameter, finding) ), From e87b1ea7b825670932ff1d56f94ddcf5d8ba1992 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 22 Jun 2020 16:24:10 +0200 Subject: [PATCH 18/55] #33 Improve the names of the generated cascading scans --- hooks/declarative-subsequent-scans/hook.ts | 12 +++++++++--- hooks/declarative-subsequent-scans/scan-helpers.js | 4 ++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/hooks/declarative-subsequent-scans/hook.ts b/hooks/declarative-subsequent-scans/hook.ts index 565ec7eb..ef09b3e3 100644 --- a/hooks/declarative-subsequent-scans/hook.ts +++ b/hooks/declarative-subsequent-scans/hook.ts @@ -40,6 +40,10 @@ interface ScanSpec { parameters: Array; } +interface ExtendedScanSpec extends ScanSpec { + generatedBy: string; +} + interface HandleArgs { scan: Scan; getFindings: () => Array; @@ -51,8 +55,9 @@ export async function handle({ scan, getFindings }: HandleArgs) { const cascadingScans = getCascadingScans(findings, cascadingRules); - for (const { scanType, parameters } of cascadingScans) { + for (const { scanType, parameters, generatedBy } of cascadingScans) { await startSubsequentSecureCodeBoxScan({ + name: `${scan.metadata.name}-${generatedBy}`, parentScan: scan, scanType, parameters, @@ -72,8 +77,8 @@ async function getCascadingRules(): Promise> { export function getCascadingScans( findings: Array, cascadingRules: Array -): Array { - const cascadingScans: Array = []; +): Array { + const cascadingScans: Array = []; for (const cascadingRule of cascadingRules) { for (const finding of findings) { @@ -86,6 +91,7 @@ export function getCascadingScans( const { scanType, parameters } = cascadingRule.spec.scanSpec; cascadingScans.push({ + generatedBy: cascadingRule.metadata.name, scanType: Mustache.render(scanType, finding), parameters: parameters.map((parameter) => Mustache.render(parameter, finding) diff --git a/hooks/declarative-subsequent-scans/scan-helpers.js b/hooks/declarative-subsequent-scans/scan-helpers.js index 8a150ada..1a08db9b 100644 --- a/hooks/declarative-subsequent-scans/scan-helpers.js +++ b/hooks/declarative-subsequent-scans/scan-helpers.js @@ -7,16 +7,16 @@ kc.loadFromDefault(); const k8sApiCRD = kc.makeApiClient(k8s.CustomObjectsApi); async function startSubsequentSecureCodeBoxScan({ + name, parentScan, scanType, parameters, }) { - const name = `${parentScan.metadata.name}-${scanType}`; const scanDefinition = { apiVersion: "execution.experimental.securecodebox.io/v1", kind: "Scan", metadata: { - name, + generateName: `${name}-`, labels: { ...parentScan.metadata.labels, }, From 112b45f2df3371a019094b4a03d97764564f88b9 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 22 Jun 2020 16:55:51 +0200 Subject: [PATCH 19/55] #33 Improved the generation of the cascading scans name If the scan was previously prefixed by the scanType, this prefix will now be replaced with the cascading Scans scanType --- .../declarative-subsequent-scans/hook.test.js | 137 +++++++++++++----- hooks/declarative-subsequent-scans/hook.ts | 37 +++-- 2 files changed, 126 insertions(+), 48 deletions(-) diff --git a/hooks/declarative-subsequent-scans/hook.test.js b/hooks/declarative-subsequent-scans/hook.test.js index 4a580464..22d2c8f6 100644 --- a/hooks/declarative-subsequent-scans/hook.test.js +++ b/hooks/declarative-subsequent-scans/hook.test.js @@ -1,60 +1,83 @@ const { getCascadingScans } = require("./hook"); -test("Should create subsequent scans for open HTTPS ports (NMAP findings)", () => { - const findings = [ - { - name: "Port 443 is open", - category: "Open Port", - attributes: { - state: "open", - hostname: "foobar.com", - port: 443, - service: "https" - } - } - ]; +let parentScan = undefined; - const cascadingRules = [ - { - apiVersion: "cascading.experimental.securecodebox.io/v1", - kind: "CascadingRule", - metadata: { - name: "tls-scans" - }, - spec: { - matches: [ +beforeEach(() => { + parentScan = { + apiVersion: "execution.experimental.securecodebox.io/v1", + kind: "Scan", + metadata: { + name: "nmap-foobar.com", + }, + spec: { + scanType: "nmap", + parameters: "foobar.com", + }, + }; +}); + +const sslyzeCascadingRules = [ + { + apiVersion: "cascading.experimental.securecodebox.io/v1", + kind: "CascadingRule", + metadata: { + name: "tls-scans", + }, + spec: { + matches: { + anyOf: [ { category: "Open Port", attributes: { port: 443, - service: "https" - } + service: "https", + }, }, { category: "Open Port", attributes: { - service: "https" - } - } + service: "https", + }, + }, ], - scanSpec: { - name: "sslyze", - parameters: ["--regular", "{{attributes.hostname}}"] - } - } - } + }, + scanSpec: { + scanType: "sslyze", + parameters: ["--regular", "{{attributes.hostname}}"], + }, + }, + }, +]; + +test("should create subsequent scans for open HTTPS ports (NMAP findings)", () => { + const findings = [ + { + name: "Port 443 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 443, + service: "https", + }, + }, ]; - const cascadedScans = getCascadingScans(findings, cascadingRules); + const cascadedScans = getCascadingScans( + parentScan, + findings, + sslyzeCascadingRules + ); expect(cascadedScans).toMatchInlineSnapshot(` Array [ Object { - "name": "sslyze", + "name": "sslyze-foobar.com-tls-scans-", "parameters": Array [ "--regular", "foobar.com", ], + "scanType": "sslyze", }, ] `); @@ -69,14 +92,50 @@ test("Should create no subsequent scans if there are no rules", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https" - } - } + service: "https", + }, + }, ]; const cascadingRules = []; - const cascadedScans = getCascadingScans(findings, cascadingRules); + const cascadedScans = getCascadingScans(parentScan, findings, cascadingRules); expect(cascadedScans).toMatchInlineSnapshot(`Array []`); }); + +test("should not try to do magic to the scan name if its something random", () => { + parentScan.metadata.name = "foobar.com"; + + const findings = [ + { + name: "Port 443 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 443, + service: "https", + }, + }, + ]; + + const cascadedScans = getCascadingScans( + parentScan, + findings, + sslyzeCascadingRules + ); + + expect(cascadedScans).toMatchInlineSnapshot(` + Array [ + Object { + "name": "foobar.com-tls-scans-", + "parameters": Array [ + "--regular", + "foobar.com", + ], + "scanType": "sslyze", + }, + ] + `); +}); diff --git a/hooks/declarative-subsequent-scans/hook.ts b/hooks/declarative-subsequent-scans/hook.ts index ef09b3e3..60adf2a6 100644 --- a/hooks/declarative-subsequent-scans/hook.ts +++ b/hooks/declarative-subsequent-scans/hook.ts @@ -16,7 +16,7 @@ interface Finding { attributes: Map; } -interface CascadingRules { +interface CascadingRule { metadata: k8s.V1ObjectMeta; spec: CascadingRuleSpec; } @@ -41,7 +41,9 @@ interface ScanSpec { } interface ExtendedScanSpec extends ScanSpec { - generatedBy: string; + // This is the name of the scan. Its not "really" part of the scan spec + // But this makes the object smaller + name: string; } interface HandleArgs { @@ -53,11 +55,11 @@ export async function handle({ scan, getFindings }: HandleArgs) { const findings = await getFindings(); const cascadingRules = await getCascadingRules(); - const cascadingScans = getCascadingScans(findings, cascadingRules); + const cascadingScans = getCascadingScans(scan, findings, cascadingRules); - for (const { scanType, parameters, generatedBy } of cascadingScans) { + for (const { name, scanType, parameters } of cascadingScans) { await startSubsequentSecureCodeBoxScan({ - name: `${scan.metadata.name}-${generatedBy}`, + name, parentScan: scan, scanType, parameters, @@ -65,9 +67,9 @@ export async function handle({ scan, getFindings }: HandleArgs) { } } -async function getCascadingRules(): Promise> { +async function getCascadingRules(): Promise> { // Explicit Cast to the proper Type - return >await getCascadingRulesFromCluster(); + return >await getCascadingRulesFromCluster(); } /** @@ -75,8 +77,9 @@ async function getCascadingRules(): Promise> { * and returns a List of Scans which should be started based on both. */ export function getCascadingScans( + parentScan: Scan, findings: Array, - cascadingRules: Array + cascadingRules: Array ): Array { const cascadingScans: Array = []; @@ -91,7 +94,7 @@ export function getCascadingScans( const { scanType, parameters } = cascadingRule.spec.scanSpec; cascadingScans.push({ - generatedBy: cascadingRule.metadata.name, + name: generateCascadingScanName(parentScan, cascadingRule), scanType: Mustache.render(scanType, finding), parameters: parameters.map((parameter) => Mustache.render(parameter, finding) @@ -103,3 +106,19 @@ export function getCascadingScans( return cascadingScans; } + +function generateCascadingScanName( + parentScan: Scan, + cascadingRule: CascadingRule +): string { + let namePrefix = parentScan.metadata.name; + + // 🧙‍ If the Parent Scan start with its scanType we'll replace it with the ScanType of the CascadingScan + if (namePrefix.startsWith(parentScan.spec.scanType)) { + namePrefix = namePrefix.replace( + parentScan.spec.scanType, + cascadingRule.spec.scanSpec.scanType + ); + } + return `${namePrefix}-${cascadingRule.metadata.name}-`; +} From 3e529b321318a3f670da811bb6c25dfbd71e4e46 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 22 Jun 2020 17:02:45 +0200 Subject: [PATCH 20/55] #33 Remove duplicated dash --- hooks/declarative-subsequent-scans/hook.test.js | 4 ++-- hooks/declarative-subsequent-scans/hook.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hooks/declarative-subsequent-scans/hook.test.js b/hooks/declarative-subsequent-scans/hook.test.js index 22d2c8f6..4935c978 100644 --- a/hooks/declarative-subsequent-scans/hook.test.js +++ b/hooks/declarative-subsequent-scans/hook.test.js @@ -72,7 +72,7 @@ test("should create subsequent scans for open HTTPS ports (NMAP findings)", () = expect(cascadedScans).toMatchInlineSnapshot(` Array [ Object { - "name": "sslyze-foobar.com-tls-scans-", + "name": "sslyze-foobar.com-tls-scans", "parameters": Array [ "--regular", "foobar.com", @@ -129,7 +129,7 @@ test("should not try to do magic to the scan name if its something random", () = expect(cascadedScans).toMatchInlineSnapshot(` Array [ Object { - "name": "foobar.com-tls-scans-", + "name": "foobar.com-tls-scans", "parameters": Array [ "--regular", "foobar.com", diff --git a/hooks/declarative-subsequent-scans/hook.ts b/hooks/declarative-subsequent-scans/hook.ts index 60adf2a6..5bb795f3 100644 --- a/hooks/declarative-subsequent-scans/hook.ts +++ b/hooks/declarative-subsequent-scans/hook.ts @@ -120,5 +120,5 @@ function generateCascadingScanName( cascadingRule.spec.scanSpec.scanType ); } - return `${namePrefix}-${cascadingRule.metadata.name}-`; + return `${namePrefix}-${cascadingRule.metadata.name}`; } From ec4082705ffc0934134b3b470eee764e0ee433b5 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 22 Jun 2020 17:05:08 +0200 Subject: [PATCH 21/55] #33 Remove confustion plural notation --- scanners/sslyze/cascading-rules/https.yaml | 2 +- scanners/sslyze/cascading-rules/mail.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scanners/sslyze/cascading-rules/https.yaml b/scanners/sslyze/cascading-rules/https.yaml index bf67b70f..bd0cfae9 100644 --- a/scanners/sslyze/cascading-rules/https.yaml +++ b/scanners/sslyze/cascading-rules/https.yaml @@ -1,7 +1,7 @@ apiVersion: "cascading.experimental.securecodebox.io/v1" kind: CascadingRule metadata: - name: "https-tls-scans" + name: "https-tls-scan" spec: matches: anyOf: diff --git a/scanners/sslyze/cascading-rules/mail.yaml b/scanners/sslyze/cascading-rules/mail.yaml index 281d3df5..823972fc 100644 --- a/scanners/sslyze/cascading-rules/mail.yaml +++ b/scanners/sslyze/cascading-rules/mail.yaml @@ -1,7 +1,7 @@ apiVersion: "cascading.experimental.securecodebox.io/v1" kind: CascadingRule metadata: - name: "smtps-tls-scans" + name: "smtps-tls-scan" spec: matches: anyOf: @@ -18,7 +18,7 @@ spec: apiVersion: "cascading.experimental.securecodebox.io/v1" kind: CascadingRule metadata: - name: "pop3s-tls-scans" + name: "pop3s-tls-scan" spec: matches: anyOf: @@ -35,7 +35,7 @@ spec: apiVersion: "cascading.experimental.securecodebox.io/v1" kind: CascadingRule metadata: - name: "imaps-tls-scans" + name: "imaps-tls-scan" spec: matches: anyOf: From d9e4be58c84100502c48e694c326d80d3fb6e27c Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 22 Jun 2020 17:32:42 +0200 Subject: [PATCH 22/55] #33 Add standard labels to cascading rules --- scanners/sslyze/cascading-rules/https.yaml | 3 +++ scanners/sslyze/cascading-rules/mail.yaml | 9 +++++++++ 2 files changed, 12 insertions(+) diff --git a/scanners/sslyze/cascading-rules/https.yaml b/scanners/sslyze/cascading-rules/https.yaml index bd0cfae9..75e33b14 100644 --- a/scanners/sslyze/cascading-rules/https.yaml +++ b/scanners/sslyze/cascading-rules/https.yaml @@ -2,6 +2,9 @@ apiVersion: "cascading.experimental.securecodebox.io/v1" kind: CascadingRule metadata: name: "https-tls-scan" + labels: + securecodebox.io/invasive: non-invasive + securecodebox.io/intensive: light spec: matches: anyOf: diff --git a/scanners/sslyze/cascading-rules/mail.yaml b/scanners/sslyze/cascading-rules/mail.yaml index 823972fc..4f2daac6 100644 --- a/scanners/sslyze/cascading-rules/mail.yaml +++ b/scanners/sslyze/cascading-rules/mail.yaml @@ -2,6 +2,9 @@ apiVersion: "cascading.experimental.securecodebox.io/v1" kind: CascadingRule metadata: name: "smtps-tls-scan" + labels: + securecodebox.io/invasive: non-invasive + securecodebox.io/intensive: light spec: matches: anyOf: @@ -19,6 +22,9 @@ apiVersion: "cascading.experimental.securecodebox.io/v1" kind: CascadingRule metadata: name: "pop3s-tls-scan" + labels: + securecodebox.io/invasive: non-invasive + securecodebox.io/intensive: light spec: matches: anyOf: @@ -36,6 +42,9 @@ apiVersion: "cascading.experimental.securecodebox.io/v1" kind: CascadingRule metadata: name: "imaps-tls-scan" + labels: + securecodebox.io/invasive: non-invasive + securecodebox.io/intensive: light spec: matches: anyOf: From 3e370dfc999cc2ba0840ecbfe1b880e7ab64ed84 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 22 Jun 2020 17:32:56 +0200 Subject: [PATCH 23/55] #33 Add cascading rules for ssh scanner --- scanners/ssh_scan/cascading-rules/ssh.yaml | 23 +++++++++++++++++++ .../ssh_scan/templates/cascading-rules.yaml | 8 +++++++ 2 files changed, 31 insertions(+) create mode 100644 scanners/ssh_scan/cascading-rules/ssh.yaml create mode 100644 scanners/ssh_scan/templates/cascading-rules.yaml diff --git a/scanners/ssh_scan/cascading-rules/ssh.yaml b/scanners/ssh_scan/cascading-rules/ssh.yaml new file mode 100644 index 00000000..126b7573 --- /dev/null +++ b/scanners/ssh_scan/cascading-rules/ssh.yaml @@ -0,0 +1,23 @@ +apiVersion: "cascading.experimental.securecodebox.io/v1" +kind: CascadingRule +metadata: + name: "ssh-scan" + labels: + securecodebox.io/invasive: non-invasive + securecodebox.io/intensive: light +spec: + matches: + anyOf: + - category: "Open Port" + attributes: + port: 22 + - category: "Open Port" + attributes: + service: "ssh" + scanSpec: + scanType: "ssh-scan" + parameters: + - "--target" + - "{{attributes.hostname}}" + - "--port" + - "{{attributes.port}}" diff --git a/scanners/ssh_scan/templates/cascading-rules.yaml b/scanners/ssh_scan/templates/cascading-rules.yaml new file mode 100644 index 00000000..ce556e7d --- /dev/null +++ b/scanners/ssh_scan/templates/cascading-rules.yaml @@ -0,0 +1,8 @@ +# The CascadingRules are not directly in the /templates directory as their curly bracket syntax clashes with helms templates ... :( +# We import them as raw files to avoid these clashes as escaping them is even more messy +{{ range $path, $_ := .Files.Glob "cascading-rules/*" }} +# Include File +{{ $.Files.Get $path }} +# Separate multiple files +--- +{{ end }} \ No newline at end of file From d1577464d31a1d502446e50a27aa850f5d640a3d Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 24 Jun 2020 18:14:21 +0200 Subject: [PATCH 24/55] #33 Add smb cascading rule for nmap --- hooks/declarative-subsequent-scans/hook.ts | 7 ++++- .../{scan-helpers.js => scan-helpers.ts} | 31 +++++++++++++++++-- scanners/nmap/cascading-rules/smb.yaml | 31 +++++++++++++++++++ scanners/nmap/templates/cascading-rules.yaml | 8 +++++ 4 files changed, 74 insertions(+), 3 deletions(-) rename hooks/declarative-subsequent-scans/{scan-helpers.js => scan-helpers.ts} (73%) create mode 100644 scanners/nmap/cascading-rules/smb.yaml create mode 100644 scanners/nmap/templates/cascading-rules.yaml diff --git a/hooks/declarative-subsequent-scans/hook.ts b/hooks/declarative-subsequent-scans/hook.ts index 5bb795f3..82892929 100644 --- a/hooks/declarative-subsequent-scans/hook.ts +++ b/hooks/declarative-subsequent-scans/hook.ts @@ -44,6 +44,9 @@ interface ExtendedScanSpec extends ScanSpec { // This is the name of the scan. Its not "really" part of the scan spec // But this makes the object smaller name: string; + + // Indicates which CascadingRule was used to generate the resulting Scan + generatedBy: string; } interface HandleArgs { @@ -57,10 +60,11 @@ export async function handle({ scan, getFindings }: HandleArgs) { const cascadingScans = getCascadingScans(scan, findings, cascadingRules); - for (const { name, scanType, parameters } of cascadingScans) { + for (const { name, scanType, parameters, generatedBy } of cascadingScans) { await startSubsequentSecureCodeBoxScan({ name, parentScan: scan, + generatedBy, scanType, parameters, }); @@ -99,6 +103,7 @@ export function getCascadingScans( parameters: parameters.map((parameter) => Mustache.render(parameter, finding) ), + generatedBy: cascadingRule.metadata.name, }); } } diff --git a/hooks/declarative-subsequent-scans/scan-helpers.js b/hooks/declarative-subsequent-scans/scan-helpers.ts similarity index 73% rename from hooks/declarative-subsequent-scans/scan-helpers.js rename to hooks/declarative-subsequent-scans/scan-helpers.ts index 1a08db9b..ca8a0a65 100644 --- a/hooks/declarative-subsequent-scans/scan-helpers.js +++ b/hooks/declarative-subsequent-scans/scan-helpers.ts @@ -1,4 +1,4 @@ -const k8s = require("@kubernetes/client-node"); +import * as k8s from "@kubernetes/client-node"; // configure k8s client const kc = new k8s.KubeConfig(); @@ -11,6 +11,7 @@ async function startSubsequentSecureCodeBoxScan({ parentScan, scanType, parameters, + generatedBy, }) { const scanDefinition = { apiVersion: "execution.experimental.securecodebox.io/v1", @@ -21,8 +22,9 @@ async function startSubsequentSecureCodeBoxScan({ ...parentScan.metadata.labels, }, annotations: { - "securecodebox.io/hook": "nmap-subsequent-scans", + "securecodebox.io/hook": "declarative-subsequent-scans", "securecodebox.io/parent-scan": parentScan.metadata.name, + "cascading.securecodebox.io/generated-by": generatedBy, }, ownerReferences: [ { @@ -80,3 +82,28 @@ async function getCascadingRulesFromCluster() { } } module.exports.getCascadingRulesFromCluster = getCascadingRulesFromCluster; + +enum LabelSelectorRequirementOperator { + In, + NotIn, + Exists, + DoesNotExist, +} + +// See: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#labelselectorrequirement-v1-meta +// Re created in TS because the included types suck 😕 +interface LabelSelectorRequirement { + key: string; + values: string; + + operator: LabelSelectorRequirementOperator; +} + +function generateLabelSelectorString( + matchExpression: Array, + matchLabels: Map +): string { + // Convert matchLabels to matchExpression syntax + matchExpression; + return ""; +} diff --git a/scanners/nmap/cascading-rules/smb.yaml b/scanners/nmap/cascading-rules/smb.yaml new file mode 100644 index 00000000..7f239ab6 --- /dev/null +++ b/scanners/nmap/cascading-rules/smb.yaml @@ -0,0 +1,31 @@ +apiVersion: "cascading.experimental.securecodebox.io/v1" +kind: CascadingRule +metadata: + name: "nmap-smb" + labels: + securecodebox.io/invasive: non-invasive + securecodebox.io/intensive: light +spec: + matches: + anyOf: + - category: "Open Port" + attributes: + port: 445 + - category: "Open Port" + attributes: + service: "microsoft-ds" + - category: "Open Port" + attributes: + service: "netbios-ssn" + scanSpec: + scanType: "nmap" + parameters: + # Treat all hosts as online -- skip host discovery + - "-Pn" + # Target Port of the finding + - "-p{{attributes.port}}" + # Use SMB Script + - "--script" + - "smb-protocols" + # Against Host + - "{{attributes.hostname}}" diff --git a/scanners/nmap/templates/cascading-rules.yaml b/scanners/nmap/templates/cascading-rules.yaml new file mode 100644 index 00000000..ce556e7d --- /dev/null +++ b/scanners/nmap/templates/cascading-rules.yaml @@ -0,0 +1,8 @@ +# The CascadingRules are not directly in the /templates directory as their curly bracket syntax clashes with helms templates ... :( +# We import them as raw files to avoid these clashes as escaping them is even more messy +{{ range $path, $_ := .Files.Glob "cascading-rules/*" }} +# Include File +{{ $.Files.Get $path }} +# Separate multiple files +--- +{{ end }} \ No newline at end of file From dcf63a78fa7dfc00c3b723da51b4de009490838c Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 24 Jun 2020 18:14:50 +0200 Subject: [PATCH 25/55] #33 Add missing @types/node dev dependency --- hooks/declarative-subsequent-scans/package-lock.json | 6 +++--- hooks/declarative-subsequent-scans/package.json | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/hooks/declarative-subsequent-scans/package-lock.json b/hooks/declarative-subsequent-scans/package-lock.json index 016c2ff4..ee488867 100644 --- a/hooks/declarative-subsequent-scans/package-lock.json +++ b/hooks/declarative-subsequent-scans/package-lock.json @@ -754,9 +754,9 @@ "integrity": "sha512-fYMgzN+9e28R81weVN49inn/u798ruU91En1ZnGvSZzCRc5jXx9B2EDhlRaWmcO1RIxFHL8AajRXzxDuJu93+A==" }, "@types/node": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.5.tgz", - "integrity": "sha512-90hiq6/VqtQgX8Sp0EzeIsv3r+ellbGj4URKj5j30tLlZvRUpnAe9YbYnjl3pJM93GyXU0tghHhvXHq+5rnCKA==" + "version": "14.0.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.14.tgz", + "integrity": "sha512-syUgf67ZQpaJj01/tRTknkMNoBBLWJOBODF0Zm4NrXmiSuxjymFrxnTu1QVYRubhVkRcZLYZG8STTwJRdVm/WQ==" }, "@types/normalize-package-data": { "version": "2.4.0", diff --git a/hooks/declarative-subsequent-scans/package.json b/hooks/declarative-subsequent-scans/package.json index e301ccf6..bb122fee 100644 --- a/hooks/declarative-subsequent-scans/package.json +++ b/hooks/declarative-subsequent-scans/package.json @@ -16,6 +16,7 @@ "mustache": "^4.0.1" }, "devDependencies": { + "@types/node": "^14.0.14", "jest": "^25.1.0", "typescript": "^3.9.5" } From b5438b424b41aae56d1e4af57dbb4a7cfab83150 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 24 Jun 2020 18:15:15 +0200 Subject: [PATCH 26/55] #33 Optimize gitignore to ignore all typescript source maps --- hooks/declarative-subsequent-scans/.gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hooks/declarative-subsequent-scans/.gitignore b/hooks/declarative-subsequent-scans/.gitignore index 4e7a3b8e..8299d9c0 100644 --- a/hooks/declarative-subsequent-scans/.gitignore +++ b/hooks/declarative-subsequent-scans/.gitignore @@ -1,3 +1,4 @@ node_modules hook.js -hook.js.map \ No newline at end of file +scan-helpers.js +*.map \ No newline at end of file From 37ce4458033ec906e6f1b9378b71f9275f22826c Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 24 Jun 2020 18:15:43 +0200 Subject: [PATCH 27/55] #33 Correct Docker Build to use typescript source instead of locally build js file --- hooks/declarative-subsequent-scans/Dockerfile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/hooks/declarative-subsequent-scans/Dockerfile b/hooks/declarative-subsequent-scans/Dockerfile index bff2f4c3..bae453a5 100644 --- a/hooks/declarative-subsequent-scans/Dockerfile +++ b/hooks/declarative-subsequent-scans/Dockerfile @@ -10,11 +10,10 @@ RUN mkdir -p /home/app WORKDIR /home/app COPY package.json package-lock.json ./ RUN npm ci -COPY hook.ts scan-helpers.js ./ +COPY hook.ts scan-helpers.ts ./ RUN npm run build FROM scbexperimental/hook-sdk-nodejs:${baseImageTag:-latest} WORKDIR /home/app/hook-wrapper/hook/ -COPY --chown=app:app scan-helpers.js ./ -COPY --from=build --chown=app:app /home/app/hook.js ./ COPY --from=install --chown=app:app /home/app/node_modules/ ./node_modules/ +COPY --from=build --chown=app:app /home/app/hook.js /home/app/scan-helpers.js ./ From b4da1ee4e0663d7632b481552433e04d3c00ae20 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 24 Jun 2020 18:29:37 +0200 Subject: [PATCH 28/55] #33 Ignore eslint for the compiled file --- .eslintignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index 48d620b0..109a112b 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ **/node_modules **/coverage -hooks/declarative-subsequent-scans/hook.js \ No newline at end of file +hooks/declarative-subsequent-scans/hook.js +hooks/declarative-subsequent-scans/scan-helpers.ts \ No newline at end of file From d3f24d52d29b7261cb57cc1e9a9ea10b2d246e7c Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 24 Jun 2020 18:31:12 +0200 Subject: [PATCH 29/55] #33 Add loop prevention to stop endless scan cycles --- .../declarative-subsequent-scans/hook.test.js | 116 +++++++++++------- hooks/declarative-subsequent-scans/hook.ts | 22 ++++ .../scan-helpers.ts | 58 ++++----- 3 files changed, 116 insertions(+), 80 deletions(-) diff --git a/hooks/declarative-subsequent-scans/hook.test.js b/hooks/declarative-subsequent-scans/hook.test.js index 4935c978..2dfc821b 100644 --- a/hooks/declarative-subsequent-scans/hook.test.js +++ b/hooks/declarative-subsequent-scans/hook.test.js @@ -1,6 +1,7 @@ const { getCascadingScans } = require("./hook"); let parentScan = undefined; +let sslyzeCascadingRules = undefined; beforeEach(() => { parentScan = { @@ -8,46 +9,47 @@ beforeEach(() => { kind: "Scan", metadata: { name: "nmap-foobar.com", + annotations: {} }, spec: { scanType: "nmap", - parameters: "foobar.com", - }, + parameters: "foobar.com" + } }; -}); -const sslyzeCascadingRules = [ - { - apiVersion: "cascading.experimental.securecodebox.io/v1", - kind: "CascadingRule", - metadata: { - name: "tls-scans", - }, - spec: { - matches: { - anyOf: [ - { - category: "Open Port", - attributes: { - port: 443, - service: "https", - }, - }, - { - category: "Open Port", - attributes: { - service: "https", - }, - }, - ], - }, - scanSpec: { - scanType: "sslyze", - parameters: ["--regular", "{{attributes.hostname}}"], + sslyzeCascadingRules = [ + { + apiVersion: "cascading.experimental.securecodebox.io/v1", + kind: "CascadingRule", + metadata: { + name: "tls-scans" }, - }, - }, -]; + spec: { + matches: { + anyOf: [ + { + category: "Open Port", + attributes: { + port: 443, + service: "https" + } + }, + { + category: "Open Port", + attributes: { + service: "https" + } + } + ] + }, + scanSpec: { + scanType: "sslyze", + parameters: ["--regular", "{{attributes.hostname}}"] + } + } + } + ]; +}); test("should create subsequent scans for open HTTPS ports (NMAP findings)", () => { const findings = [ @@ -58,9 +60,9 @@ test("should create subsequent scans for open HTTPS ports (NMAP findings)", () = state: "open", hostname: "foobar.com", port: 443, - service: "https", - }, - }, + service: "https" + } + } ]; const cascadedScans = getCascadingScans( @@ -72,6 +74,7 @@ test("should create subsequent scans for open HTTPS ports (NMAP findings)", () = expect(cascadedScans).toMatchInlineSnapshot(` Array [ Object { + "generatedBy": "tls-scans", "name": "sslyze-foobar.com-tls-scans", "parameters": Array [ "--regular", @@ -92,9 +95,9 @@ test("Should create no subsequent scans if there are no rules", () => { state: "open", hostname: "foobar.com", port: 443, - service: "https", - }, - }, + service: "https" + } + } ]; const cascadingRules = []; @@ -115,9 +118,9 @@ test("should not try to do magic to the scan name if its something random", () = state: "open", hostname: "foobar.com", port: 443, - service: "https", - }, - }, + service: "https" + } + } ]; const cascadedScans = getCascadingScans( @@ -129,6 +132,7 @@ test("should not try to do magic to the scan name if its something random", () = expect(cascadedScans).toMatchInlineSnapshot(` Array [ Object { + "generatedBy": "tls-scans", "name": "foobar.com-tls-scans", "parameters": Array [ "--regular", @@ -139,3 +143,29 @@ test("should not try to do magic to the scan name if its something random", () = ] `); }); + +test("should not start scan when the cascadingrule for it is already in the chain", () => { + parentScan.metadata.annotations["cascading.securecodebox.io/chain"] = + sslyzeCascadingRules[0].metadata.name; + + const findings = [ + { + name: "Port 443 is open", + category: "Open Port", + attributes: { + state: "open", + hostname: "foobar.com", + port: 443, + service: "https" + } + } + ]; + + const cascadedScans = getCascadingScans( + parentScan, + findings, + sslyzeCascadingRules + ); + + expect(cascadedScans).toMatchInlineSnapshot(`Array []`); +}); diff --git a/hooks/declarative-subsequent-scans/hook.ts b/hooks/declarative-subsequent-scans/hook.ts index 82892929..b6cdbaa3 100644 --- a/hooks/declarative-subsequent-scans/hook.ts +++ b/hooks/declarative-subsequent-scans/hook.ts @@ -87,7 +87,29 @@ export function getCascadingScans( ): Array { const cascadingScans: Array = []; + const cascadingRuleChain = new Set(); + + // Get the current Scan Chain (meaning which CascadingRules were used to start this scan and its parents) and convert it to a set, which makes it easier to query. + if (parentScan.metadata.annotations["cascading.securecodebox.io/chain"]) { + const chainElements = parentScan.metadata.annotations[ + "cascading.securecodebox.io/chain" + ].split(","); + + for (const element of chainElements) { + cascadingRuleChain.add(element); + } + } + for (const cascadingRule of cascadingRules) { + // Check if the Same CascadingRule was already applied in the Cascading Chain + // If it has already been used skip this rule as it could potentially lead to loops + if (cascadingRuleChain.has(cascadingRule.metadata.name)) { + console.log( + `Skipping Rule "${cascadingRule.metadata.name}" as it was already applied in this chain.` + ); + continue; + } + for (const finding of findings) { // Check if one (ore more) of the CascadingRule matchers apply to the finding const matches = cascadingRule.spec.matches.anyOf.some((matchesRule) => diff --git a/hooks/declarative-subsequent-scans/scan-helpers.ts b/hooks/declarative-subsequent-scans/scan-helpers.ts index ca8a0a65..938ef035 100644 --- a/hooks/declarative-subsequent-scans/scan-helpers.ts +++ b/hooks/declarative-subsequent-scans/scan-helpers.ts @@ -6,13 +6,21 @@ kc.loadFromDefault(); const k8sApiCRD = kc.makeApiClient(k8s.CustomObjectsApi); -async function startSubsequentSecureCodeBoxScan({ +export async function startSubsequentSecureCodeBoxScan({ name, parentScan, scanType, parameters, generatedBy, }) { + let cascadingChain: Array = []; + + if (parentScan.metadata.annotations["cascading.securecodebox.io/chain"]) { + cascadingChain = parentScan.metadata.annotations[ + "cascading.securecodebox.io/chain" + ].split(","); + } + const scanDefinition = { apiVersion: "execution.experimental.securecodebox.io/v1", kind: "Scan", @@ -23,8 +31,11 @@ async function startSubsequentSecureCodeBoxScan({ }, annotations: { "securecodebox.io/hook": "declarative-subsequent-scans", - "securecodebox.io/parent-scan": parentScan.metadata.name, - "cascading.securecodebox.io/generated-by": generatedBy, + "cascading.securecodebox.io/parent-scan": parentScan.metadata.name, + "cascading.securecodebox.io/chain": [ + ...cascadingChain, + generatedBy, + ].join(","), }, ownerReferences: [ { @@ -43,6 +54,8 @@ async function startSubsequentSecureCodeBoxScan({ }, }; + console.log(`Starting Scan ${name}`); + try { // Starting another subsequent sslyze scan based on the nmap results // found at: https://github.com/kubernetes-client/javascript/blob/79736b9a608c18d818de61a6b44503a08ea3a78f/src/gen/api/customObjectsApi.ts#L209 @@ -60,50 +73,21 @@ async function startSubsequentSecureCodeBoxScan({ } } -module.exports.startSubsequentSecureCodeBoxScan = startSubsequentSecureCodeBoxScan; - -async function getCascadingRulesFromCluster() { +export async function getCascadingRulesFromCluster() { try { const namespace = process.env["NAMESPACE"]; - const { body } = await k8sApiCRD.listNamespacedCustomObject( + const response: any = await k8sApiCRD.listNamespacedCustomObject( "cascading.experimental.securecodebox.io", "v1", namespace, "cascadingrules" ); - console.log("got CascadingRules"); - console.log(body); - console.log(JSON.stringify(body)); - return body.items; + + console.log(`Fetched ${response.body.items.length} CascadingRules`); + return response.body.items; } catch (err) { console.error("Failed to get CascadingRules from the kubernetes api"); console.error(err); process.exit(1); } } -module.exports.getCascadingRulesFromCluster = getCascadingRulesFromCluster; - -enum LabelSelectorRequirementOperator { - In, - NotIn, - Exists, - DoesNotExist, -} - -// See: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#labelselectorrequirement-v1-meta -// Re created in TS because the included types suck 😕 -interface LabelSelectorRequirement { - key: string; - values: string; - - operator: LabelSelectorRequirementOperator; -} - -function generateLabelSelectorString( - matchExpression: Array, - matchLabels: Map -): string { - // Convert matchLabels to matchExpression syntax - matchExpression; - return ""; -} From 917763c5842181734e22212fda587273e8e9d5eb Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 24 Jun 2020 18:36:28 +0200 Subject: [PATCH 30/55] #33 Use actual namespace when starting the scans --- hooks/declarative-subsequent-scans/scan-helpers.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hooks/declarative-subsequent-scans/scan-helpers.ts b/hooks/declarative-subsequent-scans/scan-helpers.ts index 938ef035..0730d040 100644 --- a/hooks/declarative-subsequent-scans/scan-helpers.ts +++ b/hooks/declarative-subsequent-scans/scan-helpers.ts @@ -6,6 +6,8 @@ kc.loadFromDefault(); const k8sApiCRD = kc.makeApiClient(k8s.CustomObjectsApi); +const namespace = process.env["NAMESPACE"]; + export async function startSubsequentSecureCodeBoxScan({ name, parentScan, @@ -57,12 +59,11 @@ export async function startSubsequentSecureCodeBoxScan({ console.log(`Starting Scan ${name}`); try { - // Starting another subsequent sslyze scan based on the nmap results - // found at: https://github.com/kubernetes-client/javascript/blob/79736b9a608c18d818de61a6b44503a08ea3a78f/src/gen/api/customObjectsApi.ts#L209 + // Submitting the Scan to the kubernetes api await k8sApiCRD.createNamespacedCustomObject( "execution.experimental.securecodebox.io", "v1", - "default", + namespace, "scans", scanDefinition, "false" @@ -75,7 +76,6 @@ export async function startSubsequentSecureCodeBoxScan({ export async function getCascadingRulesFromCluster() { try { - const namespace = process.env["NAMESPACE"]; const response: any = await k8sApiCRD.listNamespacedCustomObject( "cascading.experimental.securecodebox.io", "v1", From c983c6dae5a5f35fe48b98ffc381c01fb47f10c5 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Wed, 24 Jun 2020 18:43:24 +0200 Subject: [PATCH 31/55] #33 Ignore the correct file --- .eslintignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintignore b/.eslintignore index 109a112b..97c020b8 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,4 @@ **/node_modules **/coverage hooks/declarative-subsequent-scans/hook.js -hooks/declarative-subsequent-scans/scan-helpers.ts \ No newline at end of file +hooks/declarative-subsequent-scans/scan-helpers.js \ No newline at end of file From 7c27df53c43e1f08fde69c7bc75e5b46b963dbeb Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Thu, 25 Jun 2020 12:52:47 +0200 Subject: [PATCH 32/55] #33 Fix merge conflict --- .github/workflows/ci.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 41649356..60aea479 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -253,7 +253,6 @@ jobs: tag_with_ref: true build_args: baseImageTag=ci-local - uses: docker/build-push-action@v1 - name: "Build & Push UpdateField Hook Image" name: "Build & Push DeclarativeSubsequentScans Hook Image" with: username: ${{ secrets.DOCKER_USERNAME }} From 3549e9af543776c238427f26db33e301a0706d05 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Thu, 25 Jun 2020 14:25:38 +0200 Subject: [PATCH 33/55] #33 Exclude compiled javascript artifacts automatically --- .eslintignore | 4 ++-- hooks/declarative-subsequent-scans/.gitignore | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.eslintignore b/.eslintignore index 97c020b8..0c06d86a 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,4 @@ **/node_modules **/coverage -hooks/declarative-subsequent-scans/hook.js -hooks/declarative-subsequent-scans/scan-helpers.js \ No newline at end of file +hooks/declarative-subsequent-scans/**.js +!hooks/declarative-subsequent-scans/**.test.js diff --git a/hooks/declarative-subsequent-scans/.gitignore b/hooks/declarative-subsequent-scans/.gitignore index 8299d9c0..d72d2aa0 100644 --- a/hooks/declarative-subsequent-scans/.gitignore +++ b/hooks/declarative-subsequent-scans/.gitignore @@ -1,4 +1,4 @@ node_modules -hook.js -scan-helpers.js -*.map \ No newline at end of file +*.map +**.js +!**.test.js \ No newline at end of file From 0b881c2a05382aa850d29c229ec2da37bd2ad793 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Thu, 25 Jun 2020 14:26:12 +0200 Subject: [PATCH 34/55] #33 Add cascading attribute to select cascading rules based on labels --- operator/apis/execution/v1/scan_types.go | 2 + .../execution/v1/zz_generated.deepcopy.go | 6 +++ ...ental.securecodebox.io_cascadingrules.yaml | 47 +++++++++++++++++++ ...n.experimental.securecodebox.io_scans.yaml | 46 ++++++++++++++++++ ...ental.securecodebox.io_scheduledscans.yaml | 47 +++++++++++++++++++ 5 files changed, 148 insertions(+) diff --git a/operator/apis/execution/v1/scan_types.go b/operator/apis/execution/v1/scan_types.go index 8696acff..c77a7a44 100644 --- a/operator/apis/execution/v1/scan_types.go +++ b/operator/apis/execution/v1/scan_types.go @@ -31,6 +31,8 @@ type ScanSpec struct { ScanType string `json:"scanType,omitempty"` Parameters []string `json:"parameters,omitempty"` + + Cascades *metav1.LabelSelector `json:"cascades,omitempty"` } // ScanStatus defines the observed state of Scan diff --git a/operator/apis/execution/v1/zz_generated.deepcopy.go b/operator/apis/execution/v1/zz_generated.deepcopy.go index 3fadce74..1f89c620 100644 --- a/operator/apis/execution/v1/zz_generated.deepcopy.go +++ b/operator/apis/execution/v1/zz_generated.deepcopy.go @@ -22,6 +22,7 @@ package v1 import ( corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -345,6 +346,11 @@ func (in *ScanSpec) DeepCopyInto(out *ScanSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Cascades != nil { + in, out := &in.Cascades, &out.Cascades + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScanSpec. diff --git a/operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml b/operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml index f62a0ea3..488c26bc 100644 --- a/operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml +++ b/operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml @@ -69,6 +69,53 @@ spec: scanSpec: description: ScanSpec defines how the cascaded scan should look like properties: + cascades: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the + key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object parameters: items: type: string diff --git a/operator/config/crd/bases/execution.experimental.securecodebox.io_scans.yaml b/operator/config/crd/bases/execution.experimental.securecodebox.io_scans.yaml index f435f3b7..467bc13a 100644 --- a/operator/config/crd/bases/execution.experimental.securecodebox.io_scans.yaml +++ b/operator/config/crd/bases/execution.experimental.securecodebox.io_scans.yaml @@ -59,6 +59,52 @@ spec: spec: description: ScanSpec defines the desired state of Scan properties: + cascades: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains + values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to a + set of values. Valid operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator + is In or NotIn, the values array must be non-empty. If the + operator is Exists or DoesNotExist, the values array must + be empty. This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator is + "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object parameters: items: type: string diff --git a/operator/config/crd/bases/execution.experimental.securecodebox.io_scheduledscans.yaml b/operator/config/crd/bases/execution.experimental.securecodebox.io_scheduledscans.yaml index 0e451db1..78e07227 100644 --- a/operator/config/crd/bases/execution.experimental.securecodebox.io_scheduledscans.yaml +++ b/operator/config/crd/bases/execution.experimental.securecodebox.io_scheduledscans.yaml @@ -74,6 +74,53 @@ spec: description: Foo is an example field of ScheduledScan. Edit ScheduledScan_types.go to remove/update properties: + cascades: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the + key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object parameters: items: type: string From ee456cac1454f9c209e803b060556958b3d04aa0 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Thu, 25 Jun 2020 14:26:49 +0200 Subject: [PATCH 35/55] #33 Restrict query for CascadingRules by the Scans selector --- hooks/declarative-subsequent-scans/Dockerfile | 4 +- .../declarative-subsequent-scans/hook.test.js | 5 +- hooks/declarative-subsequent-scans/hook.ts | 65 ++------- .../kubernetes-label-selector.test.js | 133 ++++++++++++++++++ .../kubernetes-label-selector.ts | 50 +++++++ .../scan-helpers.ts | 85 +++++++++-- 6 files changed, 278 insertions(+), 64 deletions(-) create mode 100644 hooks/declarative-subsequent-scans/kubernetes-label-selector.test.js create mode 100644 hooks/declarative-subsequent-scans/kubernetes-label-selector.ts diff --git a/hooks/declarative-subsequent-scans/Dockerfile b/hooks/declarative-subsequent-scans/Dockerfile index bae453a5..9289dfe9 100644 --- a/hooks/declarative-subsequent-scans/Dockerfile +++ b/hooks/declarative-subsequent-scans/Dockerfile @@ -10,10 +10,10 @@ RUN mkdir -p /home/app WORKDIR /home/app COPY package.json package-lock.json ./ RUN npm ci -COPY hook.ts scan-helpers.ts ./ +COPY hook.ts scan-helpers.ts kubernetes-label-selector.ts ./ RUN npm run build FROM scbexperimental/hook-sdk-nodejs:${baseImageTag:-latest} WORKDIR /home/app/hook-wrapper/hook/ COPY --from=install --chown=app:app /home/app/node_modules/ ./node_modules/ -COPY --from=build --chown=app:app /home/app/hook.js /home/app/scan-helpers.js ./ +COPY --from=build --chown=app:app /home/app/hook.js /home/app/scan-helpers.js /home/app/kubernetes-label-selector.js ./ diff --git a/hooks/declarative-subsequent-scans/hook.test.js b/hooks/declarative-subsequent-scans/hook.test.js index 2dfc821b..f40ffb3f 100644 --- a/hooks/declarative-subsequent-scans/hook.test.js +++ b/hooks/declarative-subsequent-scans/hook.test.js @@ -13,7 +13,8 @@ beforeEach(() => { }, spec: { scanType: "nmap", - parameters: "foobar.com" + parameters: "foobar.com", + cascades: {} } }; @@ -74,6 +75,7 @@ test("should create subsequent scans for open HTTPS ports (NMAP findings)", () = expect(cascadedScans).toMatchInlineSnapshot(` Array [ Object { + "cascades": Object {}, "generatedBy": "tls-scans", "name": "sslyze-foobar.com-tls-scans", "parameters": Array [ @@ -132,6 +134,7 @@ test("should not try to do magic to the scan name if its something random", () = expect(cascadedScans).toMatchInlineSnapshot(` Array [ Object { + "cascades": Object {}, "generatedBy": "tls-scans", "name": "foobar.com-tls-scans", "parameters": Array [ diff --git a/hooks/declarative-subsequent-scans/hook.ts b/hooks/declarative-subsequent-scans/hook.ts index b6cdbaa3..42139226 100644 --- a/hooks/declarative-subsequent-scans/hook.ts +++ b/hooks/declarative-subsequent-scans/hook.ts @@ -1,54 +1,16 @@ import { isMatch } from "lodash"; import * as Mustache from "mustache"; -import * as k8s from "@kubernetes/client-node"; import { startSubsequentSecureCodeBoxScan, - getCascadingRulesFromCluster, + getCascadingRulesForScan, + // types + Scan, + Finding, + CascadingRule, + ExtendedScanSpec } from "./scan-helpers"; -interface Finding { - name: string; - location: string; - category: string; - severity: string; - osi_layer: string; - attributes: Map; -} - -interface CascadingRule { - metadata: k8s.V1ObjectMeta; - spec: CascadingRuleSpec; -} - -interface CascadingRuleSpec { - matches: Matches; - scanSpec: ScanSpec; -} - -interface Matches { - anyOf: Array; -} - -interface Scan { - metadata: k8s.V1ObjectMeta; - spec: ScanSpec; -} - -interface ScanSpec { - scanType: string; - parameters: Array; -} - -interface ExtendedScanSpec extends ScanSpec { - // This is the name of the scan. Its not "really" part of the scan spec - // But this makes the object smaller - name: string; - - // Indicates which CascadingRule was used to generate the resulting Scan - generatedBy: string; -} - interface HandleArgs { scan: Scan; getFindings: () => Array; @@ -56,7 +18,7 @@ interface HandleArgs { export async function handle({ scan, getFindings }: HandleArgs) { const findings = await getFindings(); - const cascadingRules = await getCascadingRules(); + const cascadingRules = await getCascadingRules(scan); const cascadingScans = getCascadingScans(scan, findings, cascadingRules); @@ -66,14 +28,14 @@ export async function handle({ scan, getFindings }: HandleArgs) { parentScan: scan, generatedBy, scanType, - parameters, + parameters }); } } -async function getCascadingRules(): Promise> { +async function getCascadingRules(scan: Scan): Promise> { // Explicit Cast to the proper Type - return >await getCascadingRulesFromCluster(); + return >await getCascadingRulesForScan(scan); } /** @@ -112,7 +74,7 @@ export function getCascadingScans( for (const finding of findings) { // Check if one (ore more) of the CascadingRule matchers apply to the finding - const matches = cascadingRule.spec.matches.anyOf.some((matchesRule) => + const matches = cascadingRule.spec.matches.anyOf.some(matchesRule => isMatch(finding, matchesRule) ); @@ -122,10 +84,11 @@ export function getCascadingScans( cascadingScans.push({ name: generateCascadingScanName(parentScan, cascadingRule), scanType: Mustache.render(scanType, finding), - parameters: parameters.map((parameter) => + parameters: parameters.map(parameter => Mustache.render(parameter, finding) ), - generatedBy: cascadingRule.metadata.name, + cascades: null, + generatedBy: cascadingRule.metadata.name }); } } diff --git a/hooks/declarative-subsequent-scans/kubernetes-label-selector.test.js b/hooks/declarative-subsequent-scans/kubernetes-label-selector.test.js new file mode 100644 index 00000000..b23d3f11 --- /dev/null +++ b/hooks/declarative-subsequent-scans/kubernetes-label-selector.test.js @@ -0,0 +1,133 @@ +const { generateLabelSelectorString } = require("./kubernetes-label-selector"); + +test("should generate a empty string if passed an empty object", () => { + expect(generateLabelSelectorString({})).toBe(""); +}); + +test("should generate basic label string for key values selector", () => { + expect( + generateLabelSelectorString({ + matchLabels: { environment: "production" } + }) + ).toBe("environment=production"); + + expect( + generateLabelSelectorString({ + matchLabels: { environment: "testing" } + }) + ).toBe("environment=testing"); +}); + +test("should generate basic label string for multiple key values selector", () => { + expect( + generateLabelSelectorString({ + matchLabels: { + environment: "production", + team: "search" + } + }) + ).toBe("environment=production,team=search"); + + expect( + generateLabelSelectorString({ + matchLabels: { + environment: "testing", + team: "payment" + } + }) + ).toBe("environment=testing,team=payment"); +}); + +test("should generate label string for set based expressions", () => { + expect( + generateLabelSelectorString({ + matchExpression: [ + { + key: "environment", + operator: "In", + values: ["testing", "development"] + } + ] + }) + ).toBe("environment in (testing,development)"); + + expect( + generateLabelSelectorString({ + matchExpression: [ + { + key: "environment", + operator: "In", + values: ["development"] + } + ] + }) + ).toBe("environment in (development)"); +}); + +test("should generate label string for set based expressions with multiple entries", () => { + expect( + generateLabelSelectorString({ + matchExpression: [ + { + key: "environment", + operator: "NotIn", + values: ["production"] + }, + { + key: "team", + operator: "In", + values: ["search", "payment"] + } + ] + }) + ).toBe("environment notin (production),team in (search,payment)"); +}); + +test("should generate label string for set based Exists and DoesNotExist operators", () => { + expect( + generateLabelSelectorString({ + matchExpression: [ + { + key: "environment", + operator: "Exists" + }, + { + key: "team", + operator: "DoesNotExist" + } + ] + }) + ).toBe("environment,!team"); +}); + +test("should generate selectors with both expression and labelMatching", () => { + expect( + generateLabelSelectorString({ + matchExpression: [ + { + key: "environment", + operator: "NotIn", + values: ["production"] + }, + { + key: "team", + operator: "In", + values: ["search", "payment"] + }, + { + key: "foobar", + operator: "Exists" + }, + { + key: "barfoo", + operator: "DoesNotExist" + } + ], + matchLabels: { + critical: "true" + } + }) + ).toBe( + "critical=true,environment notin (production),team in (search,payment),foobar,!barfoo" + ); +}); diff --git a/hooks/declarative-subsequent-scans/kubernetes-label-selector.ts b/hooks/declarative-subsequent-scans/kubernetes-label-selector.ts new file mode 100644 index 00000000..4e805b78 --- /dev/null +++ b/hooks/declarative-subsequent-scans/kubernetes-label-selector.ts @@ -0,0 +1,50 @@ +export enum LabelSelectorRequirementOperator { + In = "In", + NotIn = "NotIn", + Exists = "Exists", + DoesNotExist = "DoesNotExist" +} + +// See: https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#labelselectorrequirement-v1-meta +// Re created in TS because the included types suck 😕 +export interface LabelSelectorRequirement { + key: string; + values: Array; + + operator: LabelSelectorRequirementOperator; +} + +export interface LabelSelector { + matchExpression: Array; + matchLabels: Map; +} + +// generateLabelSelectorString transforms a kubernetes labelSelector object in to the string representation +export function generateLabelSelectorString({ + matchExpression = [], + matchLabels = new Map() +}: LabelSelector): string { + const matchLabelsSelector = Array.from(Object.entries(matchLabels)).map( + ([key, values]) => `${key}=${values}` + ); + + const matchExpressionsSelector = matchExpression.map( + ({ key, values, operator }) => { + if ( + operator === LabelSelectorRequirementOperator.In || + operator === LabelSelectorRequirementOperator.NotIn + ) { + return `${key} ${operator.toLowerCase()} (${values.join(",")})`; + } + + if (operator === LabelSelectorRequirementOperator.Exists) { + return key; + } + if (operator === LabelSelectorRequirementOperator.DoesNotExist) { + return `!${key}`; + } + } + ); + + return [...matchLabelsSelector, ...matchExpressionsSelector].join(","); +} diff --git a/hooks/declarative-subsequent-scans/scan-helpers.ts b/hooks/declarative-subsequent-scans/scan-helpers.ts index 0730d040..06552103 100644 --- a/hooks/declarative-subsequent-scans/scan-helpers.ts +++ b/hooks/declarative-subsequent-scans/scan-helpers.ts @@ -1,5 +1,10 @@ import * as k8s from "@kubernetes/client-node"; +import { + generateLabelSelectorString, + LabelSelector +} from "./kubernetes-label-selector"; + // configure k8s client const kc = new k8s.KubeConfig(); kc.loadFromDefault(); @@ -8,12 +13,55 @@ const k8sApiCRD = kc.makeApiClient(k8s.CustomObjectsApi); const namespace = process.env["NAMESPACE"]; +export interface Finding { + name: string; + location: string; + category: string; + severity: string; + osi_layer: string; + attributes: Map; +} + +export interface CascadingRule { + metadata: k8s.V1ObjectMeta; + spec: CascadingRuleSpec; +} + +export interface CascadingRuleSpec { + matches: Matches; + scanSpec: ScanSpec; +} + +export interface Matches { + anyOf: Array; +} + +export interface Scan { + metadata: k8s.V1ObjectMeta; + spec: ScanSpec; +} + +export interface ScanSpec { + scanType: string; + parameters: Array; + cascades: LabelSelector; +} + +export interface ExtendedScanSpec extends ScanSpec { + // This is the name of the scan. Its not "really" part of the scan spec + // But this makes the object smaller + name: string; + + // Indicates which CascadingRule was used to generate the resulting Scan + generatedBy: string; +} + export async function startSubsequentSecureCodeBoxScan({ name, parentScan, scanType, parameters, - generatedBy, + generatedBy }) { let cascadingChain: Array = []; @@ -29,15 +77,15 @@ export async function startSubsequentSecureCodeBoxScan({ metadata: { generateName: `${name}-`, labels: { - ...parentScan.metadata.labels, + ...parentScan.metadata.labels }, annotations: { "securecodebox.io/hook": "declarative-subsequent-scans", "cascading.securecodebox.io/parent-scan": parentScan.metadata.name, "cascading.securecodebox.io/chain": [ ...cascadingChain, - generatedBy, - ].join(","), + generatedBy + ].join(",") }, ownerReferences: [ { @@ -46,14 +94,15 @@ export async function startSubsequentSecureCodeBoxScan({ controller: true, kind: "Scan", name: parentScan.metadata.name, - uid: parentScan.metadata.uid, - }, - ], + uid: parentScan.metadata.uid + } + ] }, spec: { scanType, parameters, - }, + cascades: parentScan.spec.cascades + } }; console.log(`Starting Scan ${name}`); @@ -74,13 +123,29 @@ export async function startSubsequentSecureCodeBoxScan({ } } -export async function getCascadingRulesFromCluster() { +export async function getCascadingRulesForScan(scan: Scan) { + console.log(`CascadeConfig = "${scan.spec.cascades}"`); + if (scan.spec.cascades === undefined || scan.spec.cascades === null) { + console.log("Skipping cascades as no selector was defined."); + return []; + } + try { + const labelSelector = generateLabelSelectorString(scan.spec.cascades); + + console.log( + `Fetching CascadingScans using LabelSelector: "${labelSelector}"` + ); + const response: any = await k8sApiCRD.listNamespacedCustomObject( "cascading.experimental.securecodebox.io", "v1", namespace, - "cascadingrules" + "cascadingrules", + undefined, + undefined, + undefined, + labelSelector ); console.log(`Fetched ${response.body.items.length} CascadingRules`); From e9bd40732c297052409c65b9d73004ecf84073f6 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Thu, 25 Jun 2020 14:50:29 +0200 Subject: [PATCH 36/55] #33 Remove debug log --- hooks/declarative-subsequent-scans/scan-helpers.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/hooks/declarative-subsequent-scans/scan-helpers.ts b/hooks/declarative-subsequent-scans/scan-helpers.ts index 06552103..3a69bf9c 100644 --- a/hooks/declarative-subsequent-scans/scan-helpers.ts +++ b/hooks/declarative-subsequent-scans/scan-helpers.ts @@ -124,7 +124,6 @@ export async function startSubsequentSecureCodeBoxScan({ } export async function getCascadingRulesForScan(scan: Scan) { - console.log(`CascadeConfig = "${scan.spec.cascades}"`); if (scan.spec.cascades === undefined || scan.spec.cascades === null) { console.log("Skipping cascades as no selector was defined."); return []; From ac57d86b0648a105f91dc1c577ad63a89fe1c2f2 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Thu, 25 Jun 2020 14:51:54 +0200 Subject: [PATCH 37/55] #33 Add cascading rule for zap --- scanners/zap/cascading-rules/http.yaml | 19 +++++++++++++++++++ scanners/zap/templates/cascading-rules.yaml | 8 ++++++++ 2 files changed, 27 insertions(+) create mode 100644 scanners/zap/cascading-rules/http.yaml create mode 100644 scanners/zap/templates/cascading-rules.yaml diff --git a/scanners/zap/cascading-rules/http.yaml b/scanners/zap/cascading-rules/http.yaml new file mode 100644 index 00000000..1c1d35c1 --- /dev/null +++ b/scanners/zap/cascading-rules/http.yaml @@ -0,0 +1,19 @@ +apiVersion: "cascading.experimental.securecodebox.io/v1" +kind: CascadingRule +metadata: + name: "zap-http" + labels: + securecodebox.io/invasive: non-invasive + securecodebox.io/intensive: medium +spec: + matches: + anyOf: + - category: "Open Port" + attributes: + service: "http" + - category: "Open Port" + attributes: + service: "https" + scanSpec: + scanType: "zap-baseline" + parameters: ["-t", "{{attributes.service}}://{{attributes.hostname}}"] diff --git a/scanners/zap/templates/cascading-rules.yaml b/scanners/zap/templates/cascading-rules.yaml new file mode 100644 index 00000000..ce556e7d --- /dev/null +++ b/scanners/zap/templates/cascading-rules.yaml @@ -0,0 +1,8 @@ +# The CascadingRules are not directly in the /templates directory as their curly bracket syntax clashes with helms templates ... :( +# We import them as raw files to avoid these clashes as escaping them is even more messy +{{ range $path, $_ := .Files.Glob "cascading-rules/*" }} +# Include File +{{ $.Files.Get $path }} +# Separate multiple files +--- +{{ end }} \ No newline at end of file From 50e9998ce574b23df8fde945b8c2b856c25e3d40 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Thu, 25 Jun 2020 15:00:20 +0200 Subject: [PATCH 38/55] #33 Add CascadingRule for Nikto --- scanners/nikto/cascading-rules/http.yaml | 25 +++++++++++++++++++ scanners/nikto/templates/cascading-rules.yaml | 8 ++++++ 2 files changed, 33 insertions(+) create mode 100644 scanners/nikto/cascading-rules/http.yaml create mode 100644 scanners/nikto/templates/cascading-rules.yaml diff --git a/scanners/nikto/cascading-rules/http.yaml b/scanners/nikto/cascading-rules/http.yaml new file mode 100644 index 00000000..644aa1c1 --- /dev/null +++ b/scanners/nikto/cascading-rules/http.yaml @@ -0,0 +1,25 @@ +apiVersion: "cascading.experimental.securecodebox.io/v1" +kind: CascadingRule +metadata: + name: "nikto-http" + labels: + securecodebox.io/invasive: non-invasive + securecodebox.io/intensive: medium +spec: + matches: + anyOf: + - category: "Open Port" + attributes: + service: "http" + - category: "Open Port" + attributes: + service: "https" + scanSpec: + scanType: "nikto" + parameters: + - "-host" + - "{{attributes.hostname}}" + - "-port" + - "{{attributes.port}}" + - "-Tuning" + - "1,2,3,5,7,b" diff --git a/scanners/nikto/templates/cascading-rules.yaml b/scanners/nikto/templates/cascading-rules.yaml new file mode 100644 index 00000000..ce556e7d --- /dev/null +++ b/scanners/nikto/templates/cascading-rules.yaml @@ -0,0 +1,8 @@ +# The CascadingRules are not directly in the /templates directory as their curly bracket syntax clashes with helms templates ... :( +# We import them as raw files to avoid these clashes as escaping them is even more messy +{{ range $path, $_ := .Files.Glob "cascading-rules/*" }} +# Include File +{{ $.Files.Get $path }} +# Separate multiple files +--- +{{ end }} \ No newline at end of file From d532985d2057e5e7b38e7f1dd0d56e3771b67603 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Thu, 25 Jun 2020 17:12:16 +0200 Subject: [PATCH 39/55] #33 Fix expected values for intermediate representation --- hooks/declarative-subsequent-scans/hook.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hooks/declarative-subsequent-scans/hook.test.js b/hooks/declarative-subsequent-scans/hook.test.js index f40ffb3f..948c7558 100644 --- a/hooks/declarative-subsequent-scans/hook.test.js +++ b/hooks/declarative-subsequent-scans/hook.test.js @@ -75,7 +75,7 @@ test("should create subsequent scans for open HTTPS ports (NMAP findings)", () = expect(cascadedScans).toMatchInlineSnapshot(` Array [ Object { - "cascades": Object {}, + "cascades": null, "generatedBy": "tls-scans", "name": "sslyze-foobar.com-tls-scans", "parameters": Array [ @@ -134,7 +134,7 @@ test("should not try to do magic to the scan name if its something random", () = expect(cascadedScans).toMatchInlineSnapshot(` Array [ Object { - "cascades": Object {}, + "cascades": null, "generatedBy": "tls-scans", "name": "foobar.com-tls-scans", "parameters": Array [ From 7a02778e811a2e8ca91fa2a885a05e269ed3ffb6 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Thu, 25 Jun 2020 17:12:54 +0200 Subject: [PATCH 40/55] #33 Actually ignore the compiled typescript artefacts in eslint --- .eslintignore | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.eslintignore b/.eslintignore index 0c06d86a..4afa15ee 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,4 +1,5 @@ **/node_modules **/coverage -hooks/declarative-subsequent-scans/**.js -!hooks/declarative-subsequent-scans/**.test.js +hooks/declarative-subsequent-scans/hook.js +hooks/declarative-subsequent-scans/scan-helpers.js +hooks/declarative-subsequent-scans/kubernetes-label-selector.js From 8d016ae0d45da15674b218a6459ab0ee3fbda53d Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Thu, 25 Jun 2020 17:13:23 +0200 Subject: [PATCH 41/55] #33 Add PrinterColumns for CascadingRules in kubectl --- .../apis/cascading/v1/cascadingrule_types.go | 3 +++ ...rimental.securecodebox.io_cascadingrules.yaml | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/operator/apis/cascading/v1/cascadingrule_types.go b/operator/apis/cascading/v1/cascadingrule_types.go index 89e91219..2115bf0d 100644 --- a/operator/apis/cascading/v1/cascadingrule_types.go +++ b/operator/apis/cascading/v1/cascadingrule_types.go @@ -59,6 +59,9 @@ type CascadingRuleStatus struct { } // +kubebuilder:object:root=true +// +kubebuilder:printcolumn:name="Starts",type=string,JSONPath=`.spec.scanSpec.scanType`,description="Which Scanner is started when the CascadingRule applies" +// +kubebuilder:printcolumn:name="Invasiveness",type=string,JSONPath=`.metadata.labels.securecodebox\.io/invasive`,description="Indicates how invasive the Scanner is. Can be either 'invasive' or 'non-invasive'" +// +kubebuilder:printcolumn:name="Intensiveness",type=string,JSONPath=`.metadata.labels.securecodebox\.io/intensive`,description="Indicates how much ressource the Scanner consumes. Can be either 'light' or 'medium'" // CascadingRule is the Schema for the cascadingrules API type CascadingRule struct { diff --git a/operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml b/operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml index 488c26bc..26d8cfb7 100644 --- a/operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml +++ b/operator/config/crd/bases/cascading.experimental.securecodebox.io_cascadingrules.yaml @@ -8,6 +8,21 @@ metadata: creationTimestamp: null name: cascadingrules.cascading.experimental.securecodebox.io spec: + additionalPrinterColumns: + - JSONPath: .spec.scanSpec.scanType + description: Which Scanner is started when the CascadingRule applies + name: Starts + type: string + - JSONPath: .metadata.labels.securecodebox\.io/invasive + description: Indicates how invasive the Scanner is. Can be either 'invasive' or + 'non-invasive' + name: Invasiveness + type: string + - JSONPath: .metadata.labels.securecodebox\.io/intensive + description: Indicates how much ressource the Scanner consumes. Can be either + 'light' or 'medium' + name: Intensiveness + type: string group: cascading.experimental.securecodebox.io names: kind: CascadingRule @@ -15,6 +30,7 @@ spec: plural: cascadingrules singular: cascadingrule scope: Namespaced + subresources: {} validation: openAPIV3Schema: description: CascadingRule is the Schema for the cascadingrules API From 66afc7b2f9e462f0c0a8871f75eefc84a28aee8c Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Thu, 25 Jun 2020 17:14:51 +0200 Subject: [PATCH 42/55] #33 Update CRDs included in the helm chart --- ...ental.securecodebox.io_cascadingrules.yaml | 160 ++++++++++++++++++ ...n.experimental.securecodebox.io_scans.yaml | 46 +++++ ...ental.securecodebox.io_scheduledscans.yaml | 47 +++++ 3 files changed, 253 insertions(+) create mode 100644 operator/crds/cascading.experimental.securecodebox.io_cascadingrules.yaml diff --git a/operator/crds/cascading.experimental.securecodebox.io_cascadingrules.yaml b/operator/crds/cascading.experimental.securecodebox.io_cascadingrules.yaml new file mode 100644 index 00000000..26d8cfb7 --- /dev/null +++ b/operator/crds/cascading.experimental.securecodebox.io_cascadingrules.yaml @@ -0,0 +1,160 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.2.4 + creationTimestamp: null + name: cascadingrules.cascading.experimental.securecodebox.io +spec: + additionalPrinterColumns: + - JSONPath: .spec.scanSpec.scanType + description: Which Scanner is started when the CascadingRule applies + name: Starts + type: string + - JSONPath: .metadata.labels.securecodebox\.io/invasive + description: Indicates how invasive the Scanner is. Can be either 'invasive' or + 'non-invasive' + name: Invasiveness + type: string + - JSONPath: .metadata.labels.securecodebox\.io/intensive + description: Indicates how much ressource the Scanner consumes. Can be either + 'light' or 'medium' + name: Intensiveness + type: string + group: cascading.experimental.securecodebox.io + names: + kind: CascadingRule + listKind: CascadingRuleList + plural: cascadingrules + singular: cascadingrule + scope: Namespaced + subresources: {} + validation: + openAPIV3Schema: + description: CascadingRule is the Schema for the cascadingrules API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: CascadingRuleSpec defines the desired state of CascadingRule + properties: + matches: + description: Matches defines to which findings the CascadingRule should + apply + properties: + anyOf: + items: + description: MatchesRule is a generic map which is used to model + the structure of a finding for which the CascadingRule should + take effect + properties: + attributes: + additionalProperties: + anyOf: + - type: integer + - type: string + x-kubernetes-int-or-string: true + type: object + category: + type: string + description: + type: string + location: + type: string + name: + type: string + osi_layer: + type: string + severity: + type: string + type: object + type: array + type: object + scanSpec: + description: ScanSpec defines how the cascaded scan should look like + properties: + cascades: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the + key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object + parameters: + items: + type: string + type: array + scanType: + type: string + type: object + required: + - matches + - scanSpec + type: object + status: + description: CascadingRuleStatus defines the observed state of CascadingRule + type: object + type: object + version: v1 + versions: + - name: v1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/operator/crds/execution.experimental.securecodebox.io_scans.yaml b/operator/crds/execution.experimental.securecodebox.io_scans.yaml index f435f3b7..467bc13a 100644 --- a/operator/crds/execution.experimental.securecodebox.io_scans.yaml +++ b/operator/crds/execution.experimental.securecodebox.io_scans.yaml @@ -59,6 +59,52 @@ spec: spec: description: ScanSpec defines the desired state of Scan properties: + cascades: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that contains + values, a key, and an operator that relates the key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship to a + set of values. Valid operators are In, NotIn, Exists and + DoesNotExist. + type: string + values: + description: values is an array of string values. If the operator + is In or NotIn, the values array must be non-empty. If the + operator is Exists or DoesNotExist, the values array must + be empty. This array is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator is + "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object parameters: items: type: string diff --git a/operator/crds/execution.experimental.securecodebox.io_scheduledscans.yaml b/operator/crds/execution.experimental.securecodebox.io_scheduledscans.yaml index 0e451db1..78e07227 100644 --- a/operator/crds/execution.experimental.securecodebox.io_scheduledscans.yaml +++ b/operator/crds/execution.experimental.securecodebox.io_scheduledscans.yaml @@ -74,6 +74,53 @@ spec: description: Foo is an example field of ScheduledScan. Edit ScheduledScan_types.go to remove/update properties: + cascades: + description: A label selector is a label query over a set of resources. + The result of matchLabels and matchExpressions are ANDed. An empty + label selector matches all objects. A null label selector matches + no objects. + properties: + matchExpressions: + description: matchExpressions is a list of label selector requirements. + The requirements are ANDed. + items: + description: A label selector requirement is a selector that + contains values, a key, and an operator that relates the + key and values. + properties: + key: + description: key is the label key that the selector applies + to. + type: string + operator: + description: operator represents a key's relationship + to a set of values. Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of string values. If the + operator is In or NotIn, the values array must be non-empty. + If the operator is Exists or DoesNotExist, the values + array must be empty. This array is replaced during a + strategic merge patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} pairs. A single + {key,value} in the matchLabels map is equivalent to an element + of matchExpressions, whose key field is "key", the operator + is "In", and the values array contains only "value". The requirements + are ANDed. + type: object + type: object parameters: items: type: string From e52d84531f0cb934a3c03286d0effd9bf1b6f558 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Thu, 25 Jun 2020 17:46:42 +0200 Subject: [PATCH 43/55] #33 Implement CascadingRules for nmap hostscans on amass subdomains --- scanners/nmap/cascading-rules/hostscan.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 scanners/nmap/cascading-rules/hostscan.yaml diff --git a/scanners/nmap/cascading-rules/hostscan.yaml b/scanners/nmap/cascading-rules/hostscan.yaml new file mode 100644 index 00000000..e1e9ecfe --- /dev/null +++ b/scanners/nmap/cascading-rules/hostscan.yaml @@ -0,0 +1,19 @@ +apiVersion: "cascading.experimental.securecodebox.io/v1" +kind: CascadingRule +metadata: + name: "nmap-hostscan" + labels: + securecodebox.io/invasive: non-invasive + securecodebox.io/intensive: light +spec: + matches: + anyOf: + - category: "Subdomain" + osi_layer: "NETWORK" + scanSpec: + scanType: "nmap" + parameters: + # Treat all hosts as online -- skip host discovery + - "-Pn" + # Target Port of the finding + - "{{location}}" From b74fd3df4dbfafdf8180bb6b6c66035cee23016e Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Sat, 27 Jun 2020 21:43:47 +0200 Subject: [PATCH 44/55] #33 Add test for nmap scanning a port without a predefined port mapping --- .../nmap/parser/__testFiles__/no-service.xml | 20 ++++++++ scanners/nmap/parser/parser.test.js | 49 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 scanners/nmap/parser/__testFiles__/no-service.xml diff --git a/scanners/nmap/parser/__testFiles__/no-service.xml b/scanners/nmap/parser/__testFiles__/no-service.xml new file mode 100644 index 00000000..01f5cd0c --- /dev/null +++ b/scanners/nmap/parser/__testFiles__/no-service.xml @@ -0,0 +1,20 @@ + + + + + + + + + +
+ + + + + + + + + + diff --git a/scanners/nmap/parser/parser.test.js b/scanners/nmap/parser/parser.test.js index a5a517c3..65cb4e16 100644 --- a/scanners/nmap/parser/parser.test.js +++ b/scanners/nmap/parser/parser.test.js @@ -153,6 +153,55 @@ test("should properly parse a nmap xml without any host", async () => { expect(await parse(xmlContent)).toMatchInlineSnapshot(`Array []`); }); +test("should properly parse a nmap xml with missing service information", async () => { + const xmlContent = await readFile( + __dirname + "/__testFiles__/no-service.xml", + { + encoding: "utf8" + } + ); + + expect(await parse(xmlContent)).toMatchInlineSnapshot(` + Array [ + Object { + "attributes": Object { + "hostname": "example.com", + "ip_address": "93.184.216.34", + "mac_address": null, + "method": undefined, + "operating_system": null, + "port": 10250, + "protocol": "tcp", + "scripts": null, + "service": undefined, + "serviceProduct": null, + "serviceVersion": null, + "state": "filtered", + }, + "category": "Open Port", + "description": "Port 10250 is filtered using tcp protocol.", + "location": "tcp://93.184.216.34:10250", + "name": undefined, + "osi_layer": "NETWORK", + "severity": "INFORMATIONAL", + }, + Object { + "attributes": Object { + "hostname": "example.com", + "ip_address": "93.184.216.34", + "operating_system": null, + }, + "category": "Host", + "description": "Found a host", + "location": "example.com", + "name": "Host: example.com", + "osi_layer": "NETWORK", + "severity": "INFORMATIONAL", + }, + ] + `); +}); + test("Should properly parse a nmap xml with script specific SMB findings", async () => { const xmlContent = await readFile( __dirname + "/__testFiles__/localhost-smb-script.xml", From 98498188fd25c5fd96a7d8c48279d0afd8eb2a5e Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Sat, 27 Jun 2020 21:44:14 +0200 Subject: [PATCH 45/55] #33 Fix parser to handle empty service fields --- scanners/nmap/parser/package-lock.json | 5 +++++ scanners/nmap/parser/package.json | 1 + scanners/nmap/parser/parser.js | 13 +++++++------ 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/scanners/nmap/parser/package-lock.json b/scanners/nmap/parser/package-lock.json index c85669f7..387efeda 100644 --- a/scanners/nmap/parser/package-lock.json +++ b/scanners/nmap/parser/package-lock.json @@ -83,6 +83,11 @@ "has-symbols": "^1.0.0" } }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + }, "object-inspect": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", diff --git a/scanners/nmap/parser/package.json b/scanners/nmap/parser/package.json index 1539a535..f072a4ee 100644 --- a/scanners/nmap/parser/package.json +++ b/scanners/nmap/parser/package.json @@ -8,6 +8,7 @@ "author": "iteratec GmbH", "license": "Apache-2.0", "dependencies": { + "lodash": "^4.17.15", "xml2js": "^0.4.22" }, "devDependencies": {} diff --git a/scanners/nmap/parser/parser.js b/scanners/nmap/parser/parser.js index 0cf7471b..91019e0a 100644 --- a/scanners/nmap/parser/parser.js +++ b/scanners/nmap/parser/parser.js @@ -1,4 +1,5 @@ const xml2js = require('xml2js'); +const { get } = require('lodash'); async function parse(fileContent) { const hosts = await parseResultFile(fileContent); @@ -271,13 +272,13 @@ function parseResultFile(fileContent) { const port = parseInt(portItem.$.portid, 10); const protocol = portItem.$.protocol; - const service = portItem.service[0].$.name; - const serviceProduct = portItem.service[0].$.product; - const serviceVersion = portItem.service[0].$.version; + const service = get(portItem, ["service",0,"$","name"]); + const serviceProduct = get(portItem, ["service",0,"$","product"]); + const serviceVersion = get(portItem, ["service",0,"$","version"]); - const tunnel = portItem.service[0].$.tunnel; - const method = portItem.service[0].$.method; - const product = portItem.service[0].$.tunnel; + const tunnel = get(portItem, ["service",0,"$","tunnel"]); + const method = get(portItem, ["service",0,"$","method"]); + const product = get(portItem, ["service",0,"$","tunnel"]); const state = portItem.state[0].$.state; From ccf2399128f267a5946749aa3f84e15bc5cfb83b Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Sat, 27 Jun 2020 22:00:37 +0200 Subject: [PATCH 46/55] #33 Add cascading rule for kube-hunter --- .../cascading-rules/remote-kubernetes.yaml | 46 +++++++++++++++++++ .../templates/cascading-rules.yaml | 8 ++++ 2 files changed, 54 insertions(+) create mode 100644 scanners/kube-hunter/cascading-rules/remote-kubernetes.yaml create mode 100644 scanners/kube-hunter/templates/cascading-rules.yaml diff --git a/scanners/kube-hunter/cascading-rules/remote-kubernetes.yaml b/scanners/kube-hunter/cascading-rules/remote-kubernetes.yaml new file mode 100644 index 00000000..097939b7 --- /dev/null +++ b/scanners/kube-hunter/cascading-rules/remote-kubernetes.yaml @@ -0,0 +1,46 @@ +apiVersion: "cascading.experimental.securecodebox.io/v1" +kind: CascadingRule +metadata: + name: "kubernetes-control-plane" + labels: + securecodebox.io/invasive: non-invasive + securecodebox.io/intensive: light +spec: + matches: + anyOf: + # API Server + - category: "Open Port" + attributes: + port: 6443 + state: "open" + # etcd API + - category: "Open Port" + attributes: + port: 2379 + state: "open" + scanSpec: + scanType: "kube-hunter" + parameters: + - "--remote" + - "{{attributes.ip_address}}" +--- +apiVersion: "cascading.experimental.securecodebox.io/v1" +kind: CascadingRule +metadata: + name: "kubernetes-node" + labels: + securecodebox.io/invasive: non-invasive + securecodebox.io/intensive: light +spec: + matches: + anyOf: + # kubelet API + - category: "Open Port" + attributes: + port: 10250 + state: "open" + scanSpec: + scanType: "kube-hunter" + parameters: + - "--remote" + - "{{attributes.ip_address}}" diff --git a/scanners/kube-hunter/templates/cascading-rules.yaml b/scanners/kube-hunter/templates/cascading-rules.yaml new file mode 100644 index 00000000..ce556e7d --- /dev/null +++ b/scanners/kube-hunter/templates/cascading-rules.yaml @@ -0,0 +1,8 @@ +# The CascadingRules are not directly in the /templates directory as their curly bracket syntax clashes with helms templates ... :( +# We import them as raw files to avoid these clashes as escaping them is even more messy +{{ range $path, $_ := .Files.Glob "cascading-rules/*" }} +# Include File +{{ $.Files.Get $path }} +# Separate multiple files +--- +{{ end }} \ No newline at end of file From 4ea889b439e12c5a33b6b718b63c93df3bb4f311 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 29 Jun 2020 21:00:14 +0200 Subject: [PATCH 47/55] #33 Also ensure ports are acutally open --- scanners/nikto/cascading-rules/http.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scanners/nikto/cascading-rules/http.yaml b/scanners/nikto/cascading-rules/http.yaml index 644aa1c1..2f5c8449 100644 --- a/scanners/nikto/cascading-rules/http.yaml +++ b/scanners/nikto/cascading-rules/http.yaml @@ -10,10 +10,12 @@ spec: anyOf: - category: "Open Port" attributes: - service: "http" + service: http + state: open - category: "Open Port" attributes: - service: "https" + service: https + state: open scanSpec: scanType: "nikto" parameters: From fa551dd6e02686d3e44f545e0b513eb3a35d951c Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 29 Jun 2020 21:00:39 +0200 Subject: [PATCH 48/55] #33 Use IP Address when Hostname is not set --- scanners/nikto/cascading-rules/http.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scanners/nikto/cascading-rules/http.yaml b/scanners/nikto/cascading-rules/http.yaml index 2f5c8449..0199eb3f 100644 --- a/scanners/nikto/cascading-rules/http.yaml +++ b/scanners/nikto/cascading-rules/http.yaml @@ -20,7 +20,8 @@ spec: scanType: "nikto" parameters: - "-host" - - "{{attributes.hostname}}" + # Use Hostname if defined, fall back to ip if not defined + - "{{#attributes.hostname}}{{attributes.hostname}}{{/attributes.hostname}}{{^attributes.hostname}}{{attributes.ip_address}}{{/attributes.hostname}}" - "-port" - "{{attributes.port}}" - "-Tuning" From 80bbb4e35f8da5be83af485f36c77810458fffd7 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 29 Jun 2020 21:23:47 +0200 Subject: [PATCH 49/55] #33 Add $ attribute to hold special "helper" attributes Like "hostOrIP" --- hooks/declarative-subsequent-scans/hook.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/hooks/declarative-subsequent-scans/hook.ts b/hooks/declarative-subsequent-scans/hook.ts index 42139226..d05d9570 100644 --- a/hooks/declarative-subsequent-scans/hook.ts +++ b/hooks/declarative-subsequent-scans/hook.ts @@ -81,11 +81,20 @@ export function getCascadingScans( if (matches) { const { scanType, parameters } = cascadingRule.spec.scanSpec; + const templateArgs = { + ...finding, + // Attribute "$" hold special non finding helper attributes + $: { + hostOrIP: + finding.attributes["hostname"] || finding.attributes["ip_address"] + } + }; + cascadingScans.push({ name: generateCascadingScanName(parentScan, cascadingRule), - scanType: Mustache.render(scanType, finding), + scanType: Mustache.render(scanType, templateArgs), parameters: parameters.map(parameter => - Mustache.render(parameter, finding) + Mustache.render(parameter, templateArgs) ), cascades: null, generatedBy: cascadingRule.metadata.name From 6b6e1a2d81f04ef115d0074c7d0a78336a41ef32 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 29 Jun 2020 21:26:37 +0200 Subject: [PATCH 50/55] #33 Ensure ports across Cascading Rules are open --- scanners/nmap/cascading-rules/smb.yaml | 3 +++ scanners/ssh_scan/cascading-rules/ssh.yaml | 2 ++ scanners/sslyze/cascading-rules/https.yaml | 2 ++ scanners/sslyze/cascading-rules/mail.yaml | 6 ++++++ scanners/zap/cascading-rules/http.yaml | 6 ++++-- 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/scanners/nmap/cascading-rules/smb.yaml b/scanners/nmap/cascading-rules/smb.yaml index 7f239ab6..0823ff76 100644 --- a/scanners/nmap/cascading-rules/smb.yaml +++ b/scanners/nmap/cascading-rules/smb.yaml @@ -11,12 +11,15 @@ spec: - category: "Open Port" attributes: port: 445 + state: open - category: "Open Port" attributes: service: "microsoft-ds" + state: open - category: "Open Port" attributes: service: "netbios-ssn" + state: open scanSpec: scanType: "nmap" parameters: diff --git a/scanners/ssh_scan/cascading-rules/ssh.yaml b/scanners/ssh_scan/cascading-rules/ssh.yaml index 126b7573..2b3d0d53 100644 --- a/scanners/ssh_scan/cascading-rules/ssh.yaml +++ b/scanners/ssh_scan/cascading-rules/ssh.yaml @@ -11,9 +11,11 @@ spec: - category: "Open Port" attributes: port: 22 + state: open - category: "Open Port" attributes: service: "ssh" + state: open scanSpec: scanType: "ssh-scan" parameters: diff --git a/scanners/sslyze/cascading-rules/https.yaml b/scanners/sslyze/cascading-rules/https.yaml index 75e33b14..a73b56f5 100644 --- a/scanners/sslyze/cascading-rules/https.yaml +++ b/scanners/sslyze/cascading-rules/https.yaml @@ -11,9 +11,11 @@ spec: - category: "Open Port" attributes: port: 443 + state: open - category: "Open Port" attributes: service: "https" + state: open scanSpec: scanType: "sslyze" parameters: ["--regular", "{{attributes.hostname}}:{{attributes.port}}"] diff --git a/scanners/sslyze/cascading-rules/mail.yaml b/scanners/sslyze/cascading-rules/mail.yaml index 4f2daac6..e04047da 100644 --- a/scanners/sslyze/cascading-rules/mail.yaml +++ b/scanners/sslyze/cascading-rules/mail.yaml @@ -11,9 +11,11 @@ spec: - category: "Open Port" attributes: port: 465 + state: open - category: "Open Port" attributes: service: "smtps" + state: open scanSpec: scanType: "sslyze" parameters: ["--regular", "{{attributes.hostname}}:{{attributes.port}}"] @@ -31,9 +33,11 @@ spec: - category: "Open Port" attributes: port: 995 + state: open - category: "Open Port" attributes: service: "pop3s" + state: open scanSpec: scanType: "sslyze" parameters: ["--regular", "{{attributes.hostname}}:{{attributes.port}}"] @@ -51,9 +55,11 @@ spec: - category: "Open Port" attributes: port: 993 + state: open - category: "Open Port" attributes: service: "imaps" + state: open scanSpec: scanType: "sslyze" parameters: ["--regular", "{{attributes.hostname}}:{{attributes.port}}"] diff --git a/scanners/zap/cascading-rules/http.yaml b/scanners/zap/cascading-rules/http.yaml index 1c1d35c1..e2066aa7 100644 --- a/scanners/zap/cascading-rules/http.yaml +++ b/scanners/zap/cascading-rules/http.yaml @@ -10,10 +10,12 @@ spec: anyOf: - category: "Open Port" attributes: - service: "http" + service: http + state: open - category: "Open Port" attributes: - service: "https" + service: https + state: open scanSpec: scanType: "zap-baseline" parameters: ["-t", "{{attributes.service}}://{{attributes.hostname}}"] From 38eb28ca34954b4342abaab05add871c3996b314 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 29 Jun 2020 21:27:51 +0200 Subject: [PATCH 51/55] #33 Use `$.hostOrIP` across CascadingRules --- scanners/kube-hunter/cascading-rules/remote-kubernetes.yaml | 4 ++-- scanners/nikto/cascading-rules/http.yaml | 2 +- scanners/nmap/cascading-rules/smb.yaml | 2 +- scanners/ssh_scan/cascading-rules/ssh.yaml | 2 +- scanners/sslyze/cascading-rules/https.yaml | 2 +- scanners/sslyze/cascading-rules/mail.yaml | 6 +++--- scanners/zap/cascading-rules/http.yaml | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/scanners/kube-hunter/cascading-rules/remote-kubernetes.yaml b/scanners/kube-hunter/cascading-rules/remote-kubernetes.yaml index 097939b7..55b29aaa 100644 --- a/scanners/kube-hunter/cascading-rules/remote-kubernetes.yaml +++ b/scanners/kube-hunter/cascading-rules/remote-kubernetes.yaml @@ -22,7 +22,7 @@ spec: scanType: "kube-hunter" parameters: - "--remote" - - "{{attributes.ip_address}}" + - "{{$.hostOrIP}}" --- apiVersion: "cascading.experimental.securecodebox.io/v1" kind: CascadingRule @@ -43,4 +43,4 @@ spec: scanType: "kube-hunter" parameters: - "--remote" - - "{{attributes.ip_address}}" + - "{{$.hostOrIP}}" diff --git a/scanners/nikto/cascading-rules/http.yaml b/scanners/nikto/cascading-rules/http.yaml index 0199eb3f..28b8acd3 100644 --- a/scanners/nikto/cascading-rules/http.yaml +++ b/scanners/nikto/cascading-rules/http.yaml @@ -21,7 +21,7 @@ spec: parameters: - "-host" # Use Hostname if defined, fall back to ip if not defined - - "{{#attributes.hostname}}{{attributes.hostname}}{{/attributes.hostname}}{{^attributes.hostname}}{{attributes.ip_address}}{{/attributes.hostname}}" + - "{{$.hostOrIP}}" - "-port" - "{{attributes.port}}" - "-Tuning" diff --git a/scanners/nmap/cascading-rules/smb.yaml b/scanners/nmap/cascading-rules/smb.yaml index 0823ff76..12c2f64f 100644 --- a/scanners/nmap/cascading-rules/smb.yaml +++ b/scanners/nmap/cascading-rules/smb.yaml @@ -31,4 +31,4 @@ spec: - "--script" - "smb-protocols" # Against Host - - "{{attributes.hostname}}" + - "{{$.hostOrIP}}" diff --git a/scanners/ssh_scan/cascading-rules/ssh.yaml b/scanners/ssh_scan/cascading-rules/ssh.yaml index 2b3d0d53..67fb548d 100644 --- a/scanners/ssh_scan/cascading-rules/ssh.yaml +++ b/scanners/ssh_scan/cascading-rules/ssh.yaml @@ -20,6 +20,6 @@ spec: scanType: "ssh-scan" parameters: - "--target" - - "{{attributes.hostname}}" + - "{{$.hostOrIP}}" - "--port" - "{{attributes.port}}" diff --git a/scanners/sslyze/cascading-rules/https.yaml b/scanners/sslyze/cascading-rules/https.yaml index a73b56f5..724b2cb2 100644 --- a/scanners/sslyze/cascading-rules/https.yaml +++ b/scanners/sslyze/cascading-rules/https.yaml @@ -18,4 +18,4 @@ spec: state: open scanSpec: scanType: "sslyze" - parameters: ["--regular", "{{attributes.hostname}}:{{attributes.port}}"] + parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"] diff --git a/scanners/sslyze/cascading-rules/mail.yaml b/scanners/sslyze/cascading-rules/mail.yaml index e04047da..12c48cca 100644 --- a/scanners/sslyze/cascading-rules/mail.yaml +++ b/scanners/sslyze/cascading-rules/mail.yaml @@ -18,7 +18,7 @@ spec: state: open scanSpec: scanType: "sslyze" - parameters: ["--regular", "{{attributes.hostname}}:{{attributes.port}}"] + parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"] --- apiVersion: "cascading.experimental.securecodebox.io/v1" kind: CascadingRule @@ -40,7 +40,7 @@ spec: state: open scanSpec: scanType: "sslyze" - parameters: ["--regular", "{{attributes.hostname}}:{{attributes.port}}"] + parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"] --- apiVersion: "cascading.experimental.securecodebox.io/v1" kind: CascadingRule @@ -62,4 +62,4 @@ spec: state: open scanSpec: scanType: "sslyze" - parameters: ["--regular", "{{attributes.hostname}}:{{attributes.port}}"] + parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"] diff --git a/scanners/zap/cascading-rules/http.yaml b/scanners/zap/cascading-rules/http.yaml index e2066aa7..454f5fef 100644 --- a/scanners/zap/cascading-rules/http.yaml +++ b/scanners/zap/cascading-rules/http.yaml @@ -18,4 +18,4 @@ spec: state: open scanSpec: scanType: "zap-baseline" - parameters: ["-t", "{{attributes.service}}://{{attributes.hostname}}"] + parameters: ["-t", "{{attributes.service}}://{{$.hostOrIP}}"] From e4765303a3a0acef002e090e74918c46024524c8 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 29 Jun 2020 21:45:24 +0200 Subject: [PATCH 52/55] #33 Expand declarative hook test to capture $.hostOrIP helper --- hooks/declarative-subsequent-scans/hook.test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/hooks/declarative-subsequent-scans/hook.test.js b/hooks/declarative-subsequent-scans/hook.test.js index 948c7558..954927bf 100644 --- a/hooks/declarative-subsequent-scans/hook.test.js +++ b/hooks/declarative-subsequent-scans/hook.test.js @@ -45,7 +45,7 @@ beforeEach(() => { }, scanSpec: { scanType: "sslyze", - parameters: ["--regular", "{{attributes.hostname}}"] + parameters: ["--regular", "{{$.hostOrIP}}:{{attributes.port}}"] } } } @@ -80,7 +80,7 @@ test("should create subsequent scans for open HTTPS ports (NMAP findings)", () = "name": "sslyze-foobar.com-tls-scans", "parameters": Array [ "--regular", - "foobar.com", + "foobar.com:443", ], "scanType": "sslyze", }, @@ -118,7 +118,8 @@ test("should not try to do magic to the scan name if its something random", () = category: "Open Port", attributes: { state: "open", - hostname: "foobar.com", + hostname: undefined, + ip_address: "10.42.42.42", port: 443, service: "https" } @@ -139,7 +140,7 @@ test("should not try to do magic to the scan name if its something random", () = "name": "foobar.com-tls-scans", "parameters": Array [ "--regular", - "foobar.com", + "10.42.42.42:443", ], "scanType": "sslyze", }, From 73ff85aa2390b6fec5af0b0a088ae6c1ef472c75 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Mon, 29 Jun 2020 21:45:52 +0200 Subject: [PATCH 53/55] #33 Throw explicit error when unkown operator was specified --- .../kubernetes-label-selector.test.js | 16 ++++++++++++++++ .../kubernetes-label-selector.ts | 8 ++++++++ 2 files changed, 24 insertions(+) diff --git a/hooks/declarative-subsequent-scans/kubernetes-label-selector.test.js b/hooks/declarative-subsequent-scans/kubernetes-label-selector.test.js index b23d3f11..0d6d2498 100644 --- a/hooks/declarative-subsequent-scans/kubernetes-label-selector.test.js +++ b/hooks/declarative-subsequent-scans/kubernetes-label-selector.test.js @@ -131,3 +131,19 @@ test("should generate selectors with both expression and labelMatching", () => { "critical=true,environment notin (production),team in (search,payment),foobar,!barfoo" ); }); + +test("should throw a exception when passed a unknown operator", () => { + expect(() => + generateLabelSelectorString({ + matchExpression: [ + { + key: "environment", + operator: "FooBar", + values: ["production"] + } + ] + }) + ).toThrowErrorMatchingInlineSnapshot( + `"Unknown LabelSelector Operator \\"FooBar\\". Supported are (In, NotIn, Exists, DoesNotExist). If this is an official label selector operator in kubernetes please open up a issue in the secureCodeBox Repo."` + ); +}); diff --git a/hooks/declarative-subsequent-scans/kubernetes-label-selector.ts b/hooks/declarative-subsequent-scans/kubernetes-label-selector.ts index 4e805b78..4acf22e7 100644 --- a/hooks/declarative-subsequent-scans/kubernetes-label-selector.ts +++ b/hooks/declarative-subsequent-scans/kubernetes-label-selector.ts @@ -43,6 +43,14 @@ export function generateLabelSelectorString({ if (operator === LabelSelectorRequirementOperator.DoesNotExist) { return `!${key}`; } + + const supportedOperators = Object.values( + LabelSelectorRequirementOperator + ).join(", "); + + throw new Error( + `Unknown LabelSelector Operator "${operator}". Supported are (${supportedOperators}). If this is an official label selector operator in kubernetes please open up a issue in the secureCodeBox Repo.` + ); } ); From fa5536b79243e159f96900acffd78f45525e8980 Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Tue, 30 Jun 2020 18:11:33 +0200 Subject: [PATCH 54/55] #33 Added the why --- hooks/declarative-subsequent-scans/hook.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/hooks/declarative-subsequent-scans/hook.ts b/hooks/declarative-subsequent-scans/hook.ts index d05d9570..b9fe435f 100644 --- a/hooks/declarative-subsequent-scans/hook.ts +++ b/hooks/declarative-subsequent-scans/hook.ts @@ -113,6 +113,7 @@ function generateCascadingScanName( let namePrefix = parentScan.metadata.name; // 🧙‍ If the Parent Scan start with its scanType we'll replace it with the ScanType of the CascadingScan + // Otherwise scans like nmap-network would have cascading scans like nmap-network-nikto-http-12345 which would be confusing as it is not clear from the name anymore which scanType is actually used. if (namePrefix.startsWith(parentScan.spec.scanType)) { namePrefix = namePrefix.replace( parentScan.spec.scanType, From 17eef6ac3b62fae1c2a85408a7c7c35aadea9bbd Mon Sep 17 00:00:00 2001 From: Jannik Hollenbach <13718901+J12934@users.noreply.github.com> Date: Tue, 30 Jun 2020 18:12:25 +0200 Subject: [PATCH 55/55] #33 Link Issue in todo --- hooks/declarative-subsequent-scans/templates/NOTES.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hooks/declarative-subsequent-scans/templates/NOTES.txt b/hooks/declarative-subsequent-scans/templates/NOTES.txt index 011bcef9..5b163957 100644 --- a/hooks/declarative-subsequent-scans/templates/NOTES.txt +++ b/hooks/declarative-subsequent-scans/templates/NOTES.txt @@ -10,4 +10,4 @@ $ kubectl get cascadingrules You need to explicitly turn on scan cascading for every scan you use. You can do that by setting a label selector which matches all rules you want to use. -Find out more, on the docs: TODO \ No newline at end of file +Find out more, on the docs: TODO(https://github.com/secureCodeBox/secureCodeBox-v2-alpha/issues/46) \ No newline at end of file