Skip to content
This repository was archived by the owner on Jun 21, 2022. It is now read-only.
Open
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
35 changes: 29 additions & 6 deletions analyzer/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,12 +177,7 @@ func (r *AnalysisResult) Merge(new *AnalysisResult) {
defer r.m.Unlock()

if new.OS != nil {
// OLE also has /etc/redhat-release and it detects OLE as RHEL by mistake.
// In that case, OS must be overwritten with the content of /etc/oracle-release.
// There is the same problem between Debian and Ubuntu.
if r.OS == nil || r.OS.Family == aos.RedHat || r.OS.Family == aos.Debian {
r.OS = new.OS
}
r.OS = MergeOsVersion(r.OS, new.OS)
}

if new.Repository != nil {
Expand Down Expand Up @@ -352,3 +347,31 @@ func (ag AnalyzerGroup) AnalyzeImageConfig(targetOS types.OS, configBlob []byte)
}
return nil
}

func MergeOsVersion(old, new *types.OS) *types.OS {
switch {
case old == nil:
return new
// OLE also has /etc/redhat-release and it detects OLE as RHEL by mistake.
// In that case, OS must be overwritten with the content of /etc/oracle-release.
// There is the same problem between Debian and Ubuntu.
case old.Family == aos.RedHat, old.Family == aos.Debian:
return new
// Ubuntu has ESM program: https://ubuntu.com/security/esm
// OS version and esm status are stored in different files.
// We have to merge OS version after parsing these files.
case old.Family == aos.Ubuntu && new.Family == aos.Ubuntu:
if strings.HasSuffix(old.Name, "-ESM") { // version number with ESM suffix already stored
return old
}
if old.Extended != "" {
new.Name = new.Name + "-" + old.Extended
return new
}
if new.Extended != "" {
old.Name = old.Name + "-" + new.Extended
return old
}
}
return old
}
112 changes: 112 additions & 0 deletions analyzer/analyzer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,29 @@ func TestAnalysisResult_Merge(t *testing.T) {
},
},
},
{
name: "ubuntu must be replaced with ubuntu esm",
fields: fields{
OS: &types.OS{
Family: aos.Ubuntu, // this must be overwritten
Name: "16.04",
},
},
args: args{
new: &analyzer.AnalysisResult{
OS: &types.OS{
Family: aos.Ubuntu,
Extended: "ESM",
},
},
},
want: analyzer.AnalysisResult{
OS: &types.OS{
Family: aos.Ubuntu,
Name: "16.04-ESM",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -555,3 +578,92 @@ func TestAnalyzer_ImageConfigAnalyzerVersions(t *testing.T) {
})
}
}

func TestAnalyzer_MergeOsVersion(t *testing.T) {
tests := []struct {
name string
old *types.OS
new *types.OS
want *types.OS
}{
{
name: "Old OS is empty. New OS is Alpine",
new: &types.OS{
Family: aos.Alpine,
Name: "3.11",
},
want: &types.OS{
Family: aos.Alpine,
Name: "3.11",
},
},
{
name: "Old OS is Debian. New OS is Ubuntu",
old: &types.OS{
Family: aos.Debian,
Name: "11.2",
},
new: &types.OS{
Family: aos.Ubuntu,
Name: "16.04",
},
want: &types.OS{
Family: aos.Ubuntu,
Name: "16.04",
},
},
{
name: "Ubuntu ESM. Old OS has Extended field",
old: &types.OS{
Family: aos.Ubuntu,
Extended: "ESM",
},
new: &types.OS{
Family: aos.Ubuntu,
Name: "16.04",
},
want: &types.OS{
Family: aos.Ubuntu,
Name: "16.04-ESM",
},
},
{
name: "Ubuntu ESM. New OS has Extended field",
old: &types.OS{
Family: aos.Ubuntu,
Name: "16.04",
},
new: &types.OS{
Family: aos.Ubuntu,
Extended: "ESM",
},
want: &types.OS{
Family: aos.Ubuntu,
Name: "16.04-ESM",
},
},
{
name: "Ubuntu ESM. Old OS has full ESM name",
old: &types.OS{
Family: aos.Ubuntu,
Name: "16.04-ESM",
},
new: &types.OS{
Family: aos.Ubuntu,
Extended: "ESM",
},
want: &types.OS{
Family: aos.Ubuntu,
Name: "16.04-ESM",
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := analyzer.MergeOsVersion(test.old, test.new)

assert.Equal(t, test.want, result)
})
}
}
1 change: 1 addition & 0 deletions analyzer/os/ubuntu/testdata/esm_disabled_status.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"machine_id": "1", "version": "27.6~16.04.1", "notices": [["", "Operation in progress: ua attach"]], "account": {"external_account_ids": [], "name": "trivy", "id": "11", "created_at": "2002-06-11T05:31:56+00:00"}, "execution_details": "Operation in progress: ua attach (pid:7)", "services": [{"status_details": "CC EAL2 is not configured", "name": "cc-eal", "description_override": null, "status": "disabled", "entitled": "yes", "description": "Common Criteria EAL2 Provisioning Packages", "available": "yes"}, {"status_details": "Ubuntu Security Guide is not configured", "name": "cis", "description_override": null, "status": "disabled", "entitled": "yes", "description": "Security compliance and audit tools", "available": "yes"}, {"status_details": "UA Infra: ESM is active", "name": "esm-infra", "description_override": null, "status": "disabled", "entitled": "yes", "description": "UA Infra: Extended Security Maintenance (ESM)", "available": "yes"}, {"status_details": "Cannot install FIPS on a container.", "name": "fips", "description_override": null, "status": "n/a", "entitled": "yes", "description": "NIST-certified core packages", "available": "yes"}, {"status_details": "Cannot install FIPS Updates on a container.", "name": "fips-updates", "description_override": null, "status": "n/a", "entitled": "yes", "description": "NIST-certified core packages with priority security updates", "available": "yes"}, {"status_details": "Cannot install Livepatch on a container.", "name": "livepatch", "description_override": null, "status": "n/a", "entitled": "yes", "description": "Canonical Livepatch service", "available": "yes"}], "_doc": "Content provided in json response is currently considered Experimental and may change", "contract": {"tech_support_level": "n/a", "name": "trivy", "id": "11", "created_at": "2022-03-11T05:31:57+00:00", "products": ["free"]}, "config": {"ua_config": {"update_status_timer": 43200, "https_proxy": null, "http_proxy": null, "apt_http_proxy": null, "metering_timer": 14400, "update_messaging_timer": 21600, "apt_https_proxy": null}, "data_dir": "/var/lib/ubuntu-advantage", "log_file": "/var/log/ubuntu-advantage.log", "security_url": "https://ubuntu.com/security", "log_level": "debug", "timer_log_file": "/var/log/ubuntu-advantage-timer.log", "license_check_log_file": "/var/log/ubuntu-advantage-license-check.log", "contract_url": "https://contracts.canonical.com"}, "_schema_version": "0.1", "attached": true, "execution_status": "active", "effective": "2022-03-11T05:31:57+00:00", "config_path": "/etc/ubuntu-advantage/uaclient.conf", "origin": "free", "simulated": false, "expires": "9999-12-31T00:00:00+00:00"}
1 change: 1 addition & 0 deletions analyzer/os/ubuntu/testdata/esm_enabled_status.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"machine_id": "1", "version": "27.6~16.04.1", "notices": [["", "Operation in progress: ua attach"]], "account": {"external_account_ids": [], "name": "trivy", "id": "11", "created_at": "2002-06-11T05:31:56+00:00"}, "execution_details": "Operation in progress: ua attach (pid:7)", "services": [{"status_details": "CC EAL2 is not configured", "name": "cc-eal", "description_override": null, "status": "disabled", "entitled": "yes", "description": "Common Criteria EAL2 Provisioning Packages", "available": "yes"}, {"status_details": "Ubuntu Security Guide is not configured", "name": "cis", "description_override": null, "status": "disabled", "entitled": "yes", "description": "Security compliance and audit tools", "available": "yes"}, {"status_details": "UA Infra: ESM is active", "name": "esm-infra", "description_override": null, "status": "enabled", "entitled": "yes", "description": "UA Infra: Extended Security Maintenance (ESM)", "available": "yes"}, {"status_details": "Cannot install FIPS on a container.", "name": "fips", "description_override": null, "status": "n/a", "entitled": "yes", "description": "NIST-certified core packages", "available": "yes"}, {"status_details": "Cannot install FIPS Updates on a container.", "name": "fips-updates", "description_override": null, "status": "n/a", "entitled": "yes", "description": "NIST-certified core packages with priority security updates", "available": "yes"}, {"status_details": "Cannot install Livepatch on a container.", "name": "livepatch", "description_override": null, "status": "n/a", "entitled": "yes", "description": "Canonical Livepatch service", "available": "yes"}], "_doc": "Content provided in json response is currently considered Experimental and may change", "contract": {"tech_support_level": "n/a", "name": "trivy", "id": "11", "created_at": "2022-03-11T05:31:57+00:00", "products": ["free"]}, "config": {"ua_config": {"update_status_timer": 43200, "https_proxy": null, "http_proxy": null, "apt_http_proxy": null, "metering_timer": 14400, "update_messaging_timer": 21600, "apt_https_proxy": null}, "data_dir": "/var/lib/ubuntu-advantage", "log_file": "/var/log/ubuntu-advantage.log", "security_url": "https://ubuntu.com/security", "log_level": "debug", "timer_log_file": "/var/log/ubuntu-advantage-timer.log", "license_check_log_file": "/var/log/ubuntu-advantage-license-check.log", "contract_url": "https://contracts.canonical.com"}, "_schema_version": "0.1", "attached": true, "execution_status": "active", "effective": "2022-03-11T05:31:57+00:00", "config_path": "/etc/ubuntu-advantage/uaclient.conf", "origin": "free", "simulated": false, "expires": "9999-12-31T00:00:00+00:00"}
55 changes: 52 additions & 3 deletions analyzer/os/ubuntu/ubuntu.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,35 @@ package ubuntu
import (
"bufio"
"context"
"encoding/json"
"os"
"strings"

"github.com/aquasecurity/fanal/utils"
"golang.org/x/xerrors"

"github.com/aquasecurity/fanal/analyzer"
aos "github.com/aquasecurity/fanal/analyzer/os"
"github.com/aquasecurity/fanal/types"
"github.com/aquasecurity/fanal/utils"
)

func init() {
analyzer.RegisterAnalyzer(&ubuntuOSAnalyzer{})
}

const version = 1
const (
version = 1
ubuntuConfFilePath = "etc/lsb-release"
esmConfFilePath = "var/lib/ubuntu-advantage/status.json"
esmServiceName = "esm-infra"
esmStatusEnabled = "enabled"
esmVersionSuffix = "ESM"
)

var requiredFiles = []string{"etc/lsb-release"}
var requiredFiles = []string{
ubuntuConfFilePath,
esmConfFilePath,
}

type ubuntuOSAnalyzer struct{}

Expand All @@ -42,6 +53,19 @@ func (a ubuntuOSAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInpu
},
}, nil
}

if input.FilePath == esmConfFilePath { // Check esm config file
if esmEnabled(line) {
return &analyzer.AnalysisResult{
OS: &types.OS{
Family: aos.Ubuntu,
Extended: esmVersionSuffix,
},
}, nil
} else {
return nil, nil
}
}
}
return nil, xerrors.Errorf("ubuntu: %w", aos.AnalyzeOSError)
}
Expand All @@ -57,3 +81,28 @@ func (a ubuntuOSAnalyzer) Type() analyzer.Type {
func (a ubuntuOSAnalyzer) Version() int {
return version
}

type status struct {
Services []service `json:"services"`
}

type service struct {
Name string `json:"name"`
Status string `json:"status"`
}

func esmEnabled(config string) bool {
st := status{}

err := json.Unmarshal([]byte(config), &st)
if err != nil {
return false
}

for _, s := range st.Services { // Find ESM Service
if s.Name == esmServiceName && s.Status == esmStatusEnabled {
return true
}
}
return false
}
25 changes: 23 additions & 2 deletions analyzer/os/ubuntu/ubuntu_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ubuntu
import (
"context"
"os"
"strings"
"testing"

"github.com/aquasecurity/fanal/types"
Expand All @@ -20,12 +21,24 @@ func Test_ubuntuOSAnalyzer_Analyze(t *testing.T) {
wantErr string
}{
{
name: "happy path",
name: "happy path. Parse lsb-release file",
inputFile: "testdata/lsb-release",
want: &analyzer.AnalysisResult{
OS: &types.OS{Family: "ubuntu", Name: "18.04"},
},
},
{
name: "happy path. Parse status.json file(ESM enabled)",
inputFile: "testdata/esm_enabled_status.json",
want: &analyzer.AnalysisResult{
OS: &types.OS{Family: "ubuntu", Extended: "ESM"},
},
},
{
name: "happy path. Parse status.json file(ESM disabled)",
inputFile: "testdata/esm_disabled_status.json",
want: nil,
},
{
name: "sad path",
inputFile: "testdata/invalid",
Expand All @@ -41,7 +54,7 @@ func Test_ubuntuOSAnalyzer_Analyze(t *testing.T) {

ctx := context.Background()
got, err := a.Analyze(ctx, analyzer.AnalysisInput{
FilePath: "etc/lsb-release",
FilePath: createFilePathFromTestFile(tt.inputFile),
Content: f,
})
if tt.wantErr != "" {
Expand Down Expand Up @@ -81,3 +94,11 @@ func Test_ubuntuOSAnalyzer_Required(t *testing.T) {
})
}
}

func createFilePathFromTestFile(testFile string) string {
if strings.HasSuffix(testFile, "status.json") {
return esmConfFilePath
} else {
return ubuntuConfFilePath
}
}
3 changes: 2 additions & 1 deletion applier/docker.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package applier

import (
"fmt"
"github.com/aquasecurity/fanal/analyzer"
"strings"
"time"

Expand Down Expand Up @@ -97,7 +98,7 @@ func ApplyLayers(layers []types.BlobInfo) types.ArtifactDetail {
}

if layer.OS != nil {
mergedLayer.OS = layer.OS
mergedLayer.OS = analyzer.MergeOsVersion(mergedLayer.OS, layer.OS)
}

if layer.Repository != nil {
Expand Down
29 changes: 29 additions & 0 deletions applier/docker_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,35 @@ func TestApplyLayers(t *testing.T) {
},
},
},
{
name: "happy path with merge ubuntu version and ESM suffix",
inputLayers: []types.BlobInfo{
{
SchemaVersion: 1,
Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
OS: &types.OS{
Family: "ubuntu",
Extended: "ESM",
},
},
{
SchemaVersion: 1,
Digest: "sha256:24df0d4e20c0f42d3703bf1f1db2bdd77346c7956f74f423603d651e8e5ae8a7",
DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819",
OS: &types.OS{
Family: "ubuntu",
Name: "16.04",
},
},
},
want: types.ArtifactDetail{
OS: &types.OS{
Family: "ubuntu",
Name: "16.04-ESM",
},
},
},
{
name: "happy path with removed and updated lockfile",
inputLayers: []types.BlobInfo{
Expand Down
7 changes: 4 additions & 3 deletions types/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import (
)

type OS struct {
Family string
Name string
Eosl bool `json:"EOSL,omitempty"`
Family string
Name string
Extended string `json:"extended,omitempty"` // This field is used for enhanced security maintenance programs such as Ubuntu ESM, Debian Extended LTS.
Eosl bool `json:"EOSL,omitempty"`
}

type Repository struct {
Expand Down