Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

standardize feature names #326

Merged
merged 1 commit into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion html/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ a#title, a#title:visited, a#title:active, a#title:hover {

.filter.label {
display: inline-block;
width: 45%;
width: 44%;
text-align: right;
white-space: nowrap;
vertical-align: top;
Expand Down
8 changes: 4 additions & 4 deletions html/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -1698,7 +1698,7 @@ <h2>{{ i18n.description }}</h2>
</div>
</div>

<div v-if="$root.isLicensed(FEAT_TIMETRACKING)">
<div v-if="$root.isLicensed(FEAT_TTR)">
<div v-if="!isEdit(`comment-hours-${index}`)" class="case d-block comment-body mt-2">
<div class="ml-2 mb-2 clicktoedit" @click="startEdit('comment-hours-input', comment.hours, `comment-hours-${index}`, 'hours', modifyAssociation, ['comments', comment], false)" @keypress.space.prevent="startEdit('comment-hours-input', comment.hours, `comment-hours-${index}`, 'hours', modifyAssociation, ['comments', comment], false)">
<div :id="`comment-hours-${index}`">
Expand Down Expand Up @@ -1784,7 +1784,7 @@ <h3 class="text--primary">{{ i18n.commentAdd }}</h3>
<div class="mx-2">
<v-form ref="comments" v-model="associatedForms['comments'].valid" @submit.prevent>
<v-textarea id="comment-input" solo flat hide-details="auto" rows="5" color="text--black" :value="associatedForms['comments'].description" @change="val => associatedForms['comments'].description = val" :rules="[rules.required]" class="mb-1" persistent-hint :hint="i18n.commentDescriptionHelp"/>
<v-row v-if="$root.isLicensed(FEAT_TIMETRACKING)">
<v-row v-if="$root.isLicensed(FEAT_TTR)">
<v-col cols="4">
<v-text-field id="comment-hours-input" solo flat color="text--black" :value="associatedForms['comments'].hours" @change="val => associatedForms['comments'].hours = val" class="mb-1" persistent-hint :hint="i18n.commentHoursHelp" :rules="[rules.hours]"/>
</v-col>
Expand Down Expand Up @@ -2583,7 +2583,7 @@ <h3 class="text--primary">{{ i18n.evidenceAdd }}</h3>
<v-col cols="8"><span class="case">{{ props.item.description }}</span></v-col>
</v-row>
</div>
<div v-if="$root.isLicensed(FEAT_TIMETRACKING)">
<div v-if="$root.isLicensed(FEAT_TTR)">
<v-row no-gutters class="py-1 case tabular-row">
<v-col cols="4"><span class="case tabular-label">{{ i18n.commentHours }}:</span></v-col>
<v-col cols="8"><span class="case">{{ props.item.hours }}</span></v-col>
Expand Down Expand Up @@ -2782,7 +2782,7 @@ <h3>{{ i18n.summary }}</h3>
</div>
</v-col>
</v-row>
<v-row v-if="$root.isLicensed(FEAT_TIMETRACKING)">
<v-row v-if="$root.isLicensed(FEAT_TTR)">
<v-col cols="12">
<div>
<div class="font-weight-bold">{{ i18n.caseHours }}: </div>
Expand Down
14 changes: 12 additions & 2 deletions html/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

const routes = [];

const FEAT_TIMETRACKING = 'timetracking';
const FEAT_TTR = 'ttr';

const LICENSE_STATUS_ACTIVE = "active";
const LICENSE_STATUS_EXCEEDED = "exceeded";
Expand Down Expand Up @@ -308,6 +308,16 @@ $(document).ready(function() {
this.license = response.data.license;
this.licenseKey = response.data.licenseKey;
this.licenseStatus = response.data.licenseStatus;

if (this.licenseStatus == LICENSE_STATUS_EXCEEDED) {
this.showWarning(this.i18n.licenseExceeded);
} else if (this.licenseStatus == LICENSE_STATUS_PENDING) {
this.showWarning(this.i18n.licensePending);
} else if (this.licenseStatus == LICENSE_STATUS_EXPIRED) {
this.showWarning(this.i18n.licenseExpired);
} else if (this.licenseStatus == LICENSE_STATUS_INVALID) {
this.showWarning(this.i18n.licenseInvalid);
}
this.parameters = response.data.parameters;
this.elasticVersion = response.data.elasticVersion;
this.wazuhVersion = response.data.wazuhVersion;
Expand Down Expand Up @@ -471,7 +481,7 @@ $(document).ready(function() {
},
colorLicenseStatus(value) {
if (value == LICENSE_STATUS_ACTIVE) return "success";
if (value == LICENSE_STATUS_EXCEEDED) return "warning";
if (value == LICENSE_STATUS_EXCEEDED) return "error";
if (value == LICENSE_STATUS_EXPIRED) return "warning";
if (value == LICENSE_STATUS_INVALID) return "error";
if (value == LICENSE_STATUS_PENDING) return "warning";
Expand Down
2 changes: 1 addition & 1 deletion html/js/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ test('colorLicenseStatus', () => {
expect(app.colorLicenseStatus('foo')).toBe('info');
expect(app.colorLicenseStatus(null)).toBe('info');
expect(app.colorLicenseStatus("active")).toBe('success');
expect(app.colorLicenseStatus("exceeded")).toBe('warning');
expect(app.colorLicenseStatus("exceeded")).toBe('error');
expect(app.colorLicenseStatus("expired")).toBe('warning');
expect(app.colorLicenseStatus("invalid")).toBe('error');
expect(app.colorLicenseStatus("pending")).toBe('warning');
Expand Down
10 changes: 9 additions & 1 deletion html/js/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ const i18n = {
filterIncludeHelp: 'Adds this value as a required match in the search',
filterResults: 'Filter Results',
fingerprint: 'Fingerprint',
fps: 'Fed Info Proc Stds',
firstName: 'First Name',
flags: 'Flags',
gigabytes: 'GB',
Expand Down Expand Up @@ -440,6 +441,10 @@ const i18n = {
length: 'Length',
license: 'License',
licensee: 'Licensee',
licenseExceeded: 'This grid is currently exceeding the allowed Security Onion license allowances. Contact your Security Onion account manager for assistance in bringing the grid back into license compliance.',
licenseExpired: 'This grid is using an expired license. Contact your Security Onion account manager for assistance in renewing the license.',
licenseInvalid: 'This grid is using an invalid license. Contact your Security Onion account manager for assistance in bringing the grid back into license compliance.',
licensePending: 'This grid is using a license that has not yet become active. If this is unexpected, contact your Security Onion account manager for assistance. Alternatively, make sure your grid is synchronized to the correct time.',
licenseEffective: 'Effective',
licenseExpiration: 'Expiration',
licenseId: 'ID',
Expand All @@ -449,6 +454,7 @@ const i18n = {
licenseShort: 'ELv2',
licenseStatus: 'Status',
licenseTerms: 'License Terms',
lks: 'Encr. Disk',
loadAverage: 'Load Average',
loadAverageAbbr: 'Load 1m',
loading: 'Loading, please wait...',
Expand Down Expand Up @@ -508,6 +514,7 @@ const i18n = {
notFound: 'The selected item no longer exists',
number: 'Num',
numericOps: 'Numeric Ops',
odc: 'Open ID Connect',
of: 'of',
oidc: 'Open ID Connect (OIDC)',
oidcHelp: 'Single Sign-On via an external identity provider has been enabled for SOC. Authentication settings, such as password changes, should be performed in the external identity system unless the Security Onion administrators have enabled local password logins concurrently with SSO.',
Expand Down Expand Up @@ -688,6 +695,7 @@ const i18n = {
status: 'Status',
stenoLoss: 'Stenographer Loss',
stenoLossAbbr: 'Steno Loss',
stg: 'Security Tech Impl Guides',
summary: 'Summary',
suricataLoss: 'Suricata Loss',
suricataLossAbbr: 'Suri Loss',
Expand All @@ -699,7 +707,6 @@ const i18n = {
timePickerSample: '2006/01/02 3:04:05 PM',
timestamp: 'Timestamp',
timestampFormat: 'YYYY-MM-DD HH:mm:ss.SSS Z',
timetracking: 'Time Tracking',
timezone: 'Time Zone:',
timezoneHelp: 'Time Zone',
toggleLegend: 'Toggle Legend',
Expand Down Expand Up @@ -737,6 +744,7 @@ const i18n = {
trafficManOut: 'Outbound Mgmt Traffic',
trafficManOutAbbr: 'Mgmt Out',
transcriptCyberChefHelp: 'Send the transcript to CyberChef',
ttr: 'Time Tracking',
type: 'Type',
unaccepted: 'Pending',
unassigned: 'unassigned',
Expand Down
41 changes: 25 additions & 16 deletions licensing/license_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ const LICENSE_STATUS_INVALID = "invalid"
const LICENSE_STATUS_PENDING = "pending"
const LICENSE_STATUS_UNPROVISIONED = "unprovisioned"

const FEAT_FIPS = "fips"
const FEAT_OIDC = "oidc"
const FEAT_STIG = "stig"
const FEAT_TIMETRACKING = "timetracking"
const FEAT_FPS = "fps"
const FEAT_ODC = "odc"
const FEAT_STG = "stg"
const FEAT_LKS = "lks"
const FEAT_TTR = "ttr"

const PUBLIC_KEY = `
-----BEGIN PUBLIC KEY-----
Expand Down Expand Up @@ -163,10 +164,11 @@ func verify(key string) (*LicenseKey, error) {

func CreateAvailableFeatureList() []string {
available := make([]string, 0, 0)
available = append(available, FEAT_FIPS)
available = append(available, FEAT_OIDC)
available = append(available, FEAT_STIG)
available = append(available, FEAT_TIMETRACKING)
available = append(available, FEAT_FPS)
available = append(available, FEAT_LKS)
available = append(available, FEAT_ODC)
available = append(available, FEAT_STG)
available = append(available, FEAT_TTR)
return available
}

Expand Down Expand Up @@ -213,6 +215,7 @@ func Test(feat string, users int, nodes int, socUrl string, dataUrl string) {
licenseKey.Features = features
}

licenseKey.Id = "test"
licenseKey.Users = users
licenseKey.Nodes = nodes
licenseKey.SocUrl = socUrl
Expand Down Expand Up @@ -327,6 +330,7 @@ func startPillarMonitor() {

# This file is generated by Security Onion and contains a list of license-enabled features.
`
contents += "license_id: " + GetId() + "\n"
features := ListEnabledFeatures()
if manager.status == LICENSE_STATUS_ACTIVE {
contents += "features:\n"
Expand Down Expand Up @@ -419,7 +423,7 @@ func ListAvailableFeatures() []string {

func ListEnabledFeatures() []string {
enabled := make([]string, 0, 0)
if manager == nil {
if manager == nil || manager.status == LICENSE_STATUS_UNPROVISIONED {
return enabled
}

Expand Down Expand Up @@ -500,13 +504,9 @@ func checkExceeded(limit string, ok bool) bool {
}

if len(manager.limits) > 0 {
if manager.status == LICENSE_STATUS_ACTIVE {
manager.status = LICENSE_STATUS_EXCEEDED
}
} else {
if manager.status == LICENSE_STATUS_EXCEEDED {
manager.status = LICENSE_STATUS_ACTIVE
}
manager.status = LICENSE_STATUS_EXCEEDED
} else if manager.status == LICENSE_STATUS_EXCEEDED {
log.Info("license is no longer exceeded; restart SOC to re-initialize the license, or re-apply the license key")
}

return ok
Expand All @@ -533,6 +533,7 @@ func ValidateSocUrl(url string) bool {
return true
}
ok := manager.licenseKey.SocUrl == "" || strings.EqualFold(manager.licenseKey.SocUrl, url)
log.WithFields(log.Fields{"lic_url": manager.licenseKey.SocUrl, "found_url": url}).Info("BLAH")
return checkExceeded("socUrl", ok)
}

Expand All @@ -543,3 +544,11 @@ func ValidateDataUrl(url string) bool {
ok := manager.licenseKey.DataUrl == "" || strings.EqualFold(manager.licenseKey.DataUrl, url)
return checkExceeded("dataUrl", ok)
}

func ValidateFeature(feature string, detected bool) bool {
if manager == nil || !detected {
return true
}
ok := IsEnabled(feature)
return checkExceeded("feature_"+feature, ok)
}
67 changes: 46 additions & 21 deletions licensing/license_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,25 +123,27 @@ func TestListAvailableFeatures(tester *testing.T) {

Init(EXPIRED_KEY)
manager.status = LICENSE_STATUS_ACTIVE
assert.Len(tester, ListAvailableFeatures(), 4)
assert.Equal(tester, ListAvailableFeatures()[0], FEAT_FIPS)
assert.Equal(tester, ListAvailableFeatures()[1], FEAT_OIDC)
assert.Equal(tester, ListAvailableFeatures()[2], FEAT_STIG)
assert.Equal(tester, ListAvailableFeatures()[3], FEAT_TIMETRACKING)
assert.Len(tester, ListAvailableFeatures(), 5)
assert.Equal(tester, ListAvailableFeatures()[0], FEAT_FPS)
assert.Equal(tester, ListAvailableFeatures()[1], FEAT_LKS)
assert.Equal(tester, ListAvailableFeatures()[2], FEAT_ODC)
assert.Equal(tester, ListAvailableFeatures()[3], FEAT_STG)
assert.Equal(tester, ListAvailableFeatures()[4], FEAT_TTR)
}

func TestListEnabledFeatures(tester *testing.T) {
func TestListEnabledFeaturesUnprovisioned(tester *testing.T) {
defer setup()()

Init("")
assert.Len(tester, ListEnabledFeatures(), 4)
assert.Len(tester, ListEnabledFeatures(), 0)

Init(EXPIRED_KEY)
assert.Len(tester, ListEnabledFeatures(), 4)
assert.Equal(tester, ListEnabledFeatures()[0], FEAT_FIPS)
assert.Equal(tester, ListEnabledFeatures()[1], FEAT_OIDC)
assert.Equal(tester, ListEnabledFeatures()[2], FEAT_STIG)
assert.Equal(tester, ListEnabledFeatures()[3], FEAT_TIMETRACKING)
assert.Len(tester, ListEnabledFeatures(), 5)
assert.Equal(tester, ListEnabledFeatures()[0], FEAT_FPS)
assert.Equal(tester, ListEnabledFeatures()[1], FEAT_LKS)
assert.Equal(tester, ListEnabledFeatures()[2], FEAT_ODC)
assert.Equal(tester, ListEnabledFeatures()[3], FEAT_STG)
assert.Equal(tester, ListEnabledFeatures()[4], FEAT_TTR)

Init(EXPIRED_KEY)
manager.licenseKey.Features = append(manager.licenseKey.Features, "foo")
Expand All @@ -160,14 +162,14 @@ func TestGetLicenseKey(tester *testing.T) {
assert.Equal(tester, key.Nodes, 1)
assert.Equal(tester, key.SocUrl, "https://somewhere.invalid")
assert.Equal(tester, key.DataUrl, "https://another.place")
assert.Len(tester, key.Features, 4)
assert.Len(tester, key.Features, 5)

// Modify the returned object and make sure it doesn't affect the orig object
key.Users = 100
key.Features = append(key.Features, "foo")
assert.Equal(tester, GetLicenseKey().Users, 1)
assert.Len(tester, key.Features, 5)
assert.Len(tester, GetLicenseKey().Features, 4)
assert.Len(tester, key.Features, 6)
assert.Len(tester, GetLicenseKey().Features, 5)
}

func TestGetStatus(tester *testing.T) {
Expand Down Expand Up @@ -265,18 +267,39 @@ func TestValidateDataUrl(tester *testing.T) {
assert.False(tester, ValidateDataUrl("bar"))
}

func TestValidateFeature(tester *testing.T) {
defer setup()()

Test("stg", 0, 0, "", "")

awaitPillarMonitor()
manager.licenseKey.Features = append(manager.licenseKey.Features, FEAT_STG)
assert.True(tester, ValidateFeature(FEAT_STG, false))
assert.Len(tester, manager.limits, 0)
assert.True(tester, ValidateFeature(FEAT_STG, true))
assert.Len(tester, manager.limits, 0)
assert.True(tester, ValidateFeature(FEAT_LKS, false))
assert.Len(tester, manager.limits, 0)
assert.False(tester, ValidateFeature(FEAT_LKS, true))
assert.Len(tester, manager.limits, 1)
assert.False(tester, ValidateFeature("", true))
assert.Len(tester, manager.limits, 2)
assert.False(tester, ValidateFeature("bar", true))
assert.Len(tester, manager.limits, 3)
}

func TestPillarMonitor(tester *testing.T) {
defer setup()()

Test("stig", 0, 0, "", "")
Test("stg", 0, 0, "", "")

awaitPillarMonitor()
assert.Equal(tester, manager.status, LICENSE_STATUS_ACTIVE)
contents, _ := os.ReadFile(pillarFilename)

expected := `
features:
- stig
- stg
`

assert.Contains(tester, string(contents), expected)
Expand Down Expand Up @@ -304,11 +327,13 @@ func TestPillarMonitorAllFeatures(tester *testing.T) {
# software that is protected by the license key."

# This file is generated by Security Onion and contains a list of license-enabled features.
license_id: test
features:
- fips
- oidc
- stig
- timetracking
- fps
- lks
- odc
- stg
- ttr
`

assert.Equal(tester, expected, string(contents))
Expand Down
2 changes: 2 additions & 0 deletions model/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ type Node struct {
DiskUsedElasticGB float64 `json:"diskUsedElasticGB"`
DiskUsedInfluxDbGB float64 `json:"diskUsedInfluxDbGB"`
HighstateAgeSeconds int `json:"highstateAgeSeconds"`
LksEnabled int `json:"lksEnabled"`
FpsEnabled int `json:"fpsEnabled"`
}

func NewNode(id string) *Node {
Expand Down
2 changes: 1 addition & 1 deletion server/modules/elastic/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ func convertElasticEventToComment(event *model.EventRecord, schemaPrefix string)
if value, ok := event.Payload[schemaPrefix+"comment.caseId"]; ok {
obj.CaseId = value.(string)
}
if licensing.IsEnabled(licensing.FEAT_TIMETRACKING) {
if licensing.IsEnabled(licensing.FEAT_TTR) {
if value, ok := event.Payload[schemaPrefix+"comment.hours"]; ok {
obj.Hours = value.(float64)
}
Expand Down
2 changes: 1 addition & 1 deletion server/modules/elastic/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ func TestConvertElasticEventToComment(tester *testing.T) {
myTime := time.Now()
myCreateTime := myTime.Add(time.Hour * -1)

licensing.Test(licensing.FEAT_TIMETRACKING, 0, 0, "", "")
licensing.Test(licensing.FEAT_TTR, 0, 0, "", "")

event := &model.EventRecord{}
event.Payload = make(map[string]interface{})
Expand Down
Loading
Loading