diff --git a/go.mod b/go.mod index ddc211a..4b222a6 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,8 @@ go 1.23.2 require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0 - github.com/compliance-framework/agent v0.0.15 - github.com/compliance-framework/configuration-service v0.0.5 + github.com/compliance-framework/agent v0.1.1 + github.com/compliance-framework/configuration-service v0.1.1 github.com/google/uuid v1.6.0 github.com/hashicorp/go-hclog v1.5.0 github.com/hashicorp/go-plugin v1.6.2 @@ -21,7 +21,6 @@ require ( github.com/agnivade/levenshtein v1.2.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/defenseunicorns/go-oscal v0.6.2 // indirect github.com/fatih/color v1.15.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-logr/logr v1.4.2 // indirect diff --git a/go.sum b/go.sum index a25da23..2d74902 100644 --- a/go.sum +++ b/go.sum @@ -4,12 +4,20 @@ github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WW github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2 h1:F0gBpfdPLGsw+nsgk6aqqkZS1jiixa5WwFe3fk/T3Ys= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.2/go.mod h1:SqINnQ9lVVdRlyC8cd1lCI0SdX4n2paeABd2K8ggfnE= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= +github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.0.0 h1:lMW1lD/17LUA5z1XTURo7LcVG2ICBPlyMHjIUrcFZNQ= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.0.0/go.mod h1:ceIuwmxDWptoW3eCqSXlnPsZFKh4X+R38dWPv7GS9Vs= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0 h1:QM6sE5k2ZT/vI5BEe0r7mqjsUSnhVBFbOsVkEuaEfiA= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/network/armnetwork v1.1.0/go.mod h1:243D9iHbcQXoFUtgHJwL7gl2zx1aDuDMjvBZVGr2uW0= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0/go.mod h1:s1tW/At+xHqjNFvWU4G0c0Qv33KOhvbGNj0RCTQDV8s= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM= +github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3 h1:H5xDQaE3XowWfhZRUpnfC+rGZMEVoSiji+b+/HFAPU4= github.com/AzureAD/microsoft-authentication-library-for-go v1.3.3/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= @@ -38,10 +46,10 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/compliance-framework/agent v0.0.15 h1:VEB3xI3VSgTeudw8L+Czv9VCMt6vM7Nutd+JeMjWjuk= -github.com/compliance-framework/agent v0.0.15/go.mod h1:/ZxHkJJm/wthxm+W7atUgSMfL2217cCaBgN6dfsDSYo= -github.com/compliance-framework/configuration-service v0.0.5 h1:vK9mSb8dzaaTt+hd+g2g3+8nvfr/Ha5pMXB4yiYMA64= -github.com/compliance-framework/configuration-service v0.0.5/go.mod h1:irXS+U+ZGaNrOmaNqb+pMmo+4BxSZJ0/vs4ne/5qVJc= +github.com/compliance-framework/agent v0.1.1 h1:uQ4idgwOMqrgM0JeYCtBv20HZoMymsH2nownrkl457w= +github.com/compliance-framework/agent v0.1.1/go.mod h1:jy/26xgTx9+at64ipTV1oo80pTVyhtlZaSMViQ3cVVQ= +github.com/compliance-framework/configuration-service v0.1.1 h1:p/r5vq1FLe0S8j/kLhth4Dvda8xajVPOBjnO9QauMjM= +github.com/compliance-framework/configuration-service v0.1.1/go.mod h1:tLKJKXbQbY9Pg/e3BJtJVkqxaejXJMHoE8Yp0NW4lDE= github.com/containerd/containerd v1.7.24 h1:zxszGrGjrra1yYJW/6rhm9cJ1ZQ8rkKBR48brqsa7nA= github.com/containerd/containerd v1.7.24/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= @@ -60,6 +68,8 @@ github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0 github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw= github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= @@ -114,7 +124,6 @@ github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= @@ -146,6 +155,8 @@ github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgf github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6 h1:IsMZxCuZqKuao2vNdfD82fjjgPLfyHLpR41Z88viRWs= +github.com/keybase/go-keychain v0.0.0-20231219164618-57a3676c3af6/go.mod h1:3VeWNIJaW+O5xpRQbPp0Ybqu1vJd/pm7s2F473HRrkw= github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -222,6 +233,8 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= +github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4= @@ -297,18 +310,14 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -318,12 +327,8 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= -golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= diff --git a/internal/util.go b/internal/util.go new file mode 100644 index 0000000..feb597f --- /dev/null +++ b/internal/util.go @@ -0,0 +1,15 @@ +package internal + +func MergeMaps(maps ...map[string]string) map[string]string { + result := make(map[string]string) + for _, imap := range maps { + for k, v := range imap { + result[k] = v + } + } + return result +} + +func StringAddressed(str string) *string { + return &str +} diff --git a/main.go b/main.go index 32e6777..f9d8193 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/compliance-framework/plugin-azure-networking-security/internal" "os" "time" @@ -16,7 +17,6 @@ import ( "github.com/google/uuid" "github.com/hashicorp/go-hclog" goplugin "github.com/hashicorp/go-plugin" - protolang "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -39,20 +39,20 @@ func (l *CompliancePlugin) Eval(request *proto.EvalRequest, apiHelper runner.Api ctx := context.TODO() startTime := time.Now() evalStatus := proto.ExecutionStatus_SUCCESS - var errAcc error + var accumulatedErrors error cred, err := azidentity.NewDefaultAzureCredential(nil) if err != nil { l.logger.Error("unable to get Azure credentials", "error", err) evalStatus = proto.ExecutionStatus_FAILURE - errAcc = errors.Join(errAcc, err) + accumulatedErrors = errors.Join(accumulatedErrors, err) } client, err := armnetwork.NewSecurityGroupsClient(os.Getenv("AZURE_SUBSCRIPTION_ID"), cred, nil) if err != nil { l.logger.Error("unable to create Azure security groups client", "error", err) evalStatus = proto.ExecutionStatus_FAILURE - errAcc = errors.Join(errAcc, err) + accumulatedErrors = errors.Join(accumulatedErrors, err) } // Get security groups @@ -63,14 +63,12 @@ func (l *CompliancePlugin) Eval(request *proto.EvalRequest, apiHelper runner.Api if err != nil { l.logger.Error("unable to list security groups", "error", err) evalStatus = proto.ExecutionStatus_FAILURE - errAcc = errors.Join(errAcc, err) + accumulatedErrors = errors.Join(accumulatedErrors, err) break } // Parse security groups for _, group := range page.Value { - l.logger.Debug("security group", group) - var tags []Tag for key, value := range group.Tags { tags = append(tags, Tag{Key: key, Value: *value}) @@ -83,195 +81,225 @@ func (l *CompliancePlugin) Eval(request *proto.EvalRequest, apiHelper runner.Api "provisioningState": group.Properties.ProvisioningState, } - // Append security group to list with all data from Azure API - securityGroups = append(securityGroups, map[string]interface{}{ - "SecurityGroupID": *group.ID, - "Location": *group.Location, - "Name": *group.Name, - "Properties": properties, - "Tags": tags, - "Type": *group.Type, - "DefaultSecurityRules": group.Properties.DefaultSecurityRules, - "SecurityRules": group.Properties.SecurityRules, - "ProvisioningState": group.Properties.ProvisioningState, - }) - } - } - - l.logger.Debug("evaluating data", securityGroups) - - // Run policy checks - for _, instance := range securityGroups { - for _, policyPath := range request.GetPolicyPaths() { - results, err := policyManager.New(ctx, l.logger, policyPath).Execute(ctx, "compliance_plugin", instance) - if err != nil { - l.logger.Error("policy evaluation failed", "error", err) - evalStatus = proto.ExecutionStatus_FAILURE - errAcc = errors.Join(errAcc, err) - continue + subjectAttributeMap := map[string]string{ + "type": "aws", + "service": "security-group", + "instance-id": *group.ID, + "instance-name": *group.Name, } - - // Build and send results (this is also from your existing logic) - assessmentResult := runner.NewCallableAssessmentResult() - assessmentResult.Title = "Security Group checks - Azure plugin" - - for _, result := range results { - - // There are no violations reported from the policies. - // We'll send the observation back to the agent - if len(result.Violations) == 0 { - title := "The plugin succeeded. No compliance issues to report." - assessmentResult.AddObservation(&proto.Observation{ - Uuid: uuid.New().String(), - Title: &title, - Description: "The plugin policies did not return any violations. The configuration is in compliance with policies.", - Collected: timestamppb.New(time.Now()), - Expires: timestamppb.New(time.Now().AddDate(0, 1, 0)), // Add one month for the expiration - RelevantEvidence: []*proto.RelevantEvidence{ - { - Description: fmt.Sprintf("Policy %v was evaluated, and no violations were found on machineId: %s", result.Policy.Package.PurePackage(), "ID:12345"), - }, + subjects := []*proto.SubjectReference{ + { + Type: "aws-security-group", + Attributes: subjectAttributeMap, + Title: internal.StringAddressed("AWS Security Group"), + Props: []*proto.Property{ + { + Name: "security-group-id", + Value: *group.ID, }, - Labels: map[string]string{ - "package": string(result.Policy.Package), - "type": "azure", - "service": "security-groups", - "security_group_id": fmt.Sprintf("%v", instance["SecurityGroupID"]), + { + Name: "security-group-name", + Value: *group.Name, }, - }) - - status := runner.FindingTargetStatusSatisfied - assessmentResult.AddFinding(&proto.Finding{ - Title: fmt.Sprintf("No violations found on %s", result.Policy.Package.PurePackage()), - Description: fmt.Sprintf("No violations found on the %s policy within the Template Compliance Plugin.", result.Policy.Package.PurePackage()), - Target: &proto.FindingTarget{ - Status: &proto.ObjectiveStatus{ - State: status, - }, + }, + }, + } + actors := []*proto.OriginActor{ + { + Title: "The Continuous Compliance Framework", + Type: "assessment-platform", + Links: []*proto.Link{ + { + Href: "https://compliance-framework.github.io/docs/", + Rel: internal.StringAddressed("reference"), + Text: internal.StringAddressed("The Continuous Compliance Framework"), }, - Labels: map[string]string{ - "package": string(result.Policy.Package), - "type": "azure", - "service": "security-groups", - "security_group_id": fmt.Sprintf("%v", instance["SecurityGroupID"]), + }, + }, + { + Title: "Continuous Compliance Framework - Local SSH Plugin", + Type: "tool", + Links: []*proto.Link{ + { + Href: "https://github.com/compliance-framework/plugin-local-ssh", + Rel: internal.StringAddressed("reference"), + Text: internal.StringAddressed("The Continuous Compliance Framework' Local SSH Plugin"), }, - }) + }, + }, + } + components := []*proto.ComponentReference{ + { + Identifier: "common-components/aws-security-group", + }, + } + + activities := make([]*proto.Activity, 0) + findings := make([]*proto.Finding, 0) + observations := make([]*proto.Observation, 0) + + for _, policyPath := range request.GetPolicyPaths() { + // Explicitly reset steps to make things readable + steps := make([]*proto.Step, 0) + steps = append(steps, &proto.Step{ + Title: "Compile policy bundle", + Description: "Using a locally addressable policy path, compile the policy files to an in memory executable.", + }) + steps = append(steps, &proto.Step{ + Title: "Execute policy bundle", + Description: "Using previously collected JSON-formatted SSH configuration, execute the compiled policies", + }) + activities = append(activities, &proto.Activity{ + Title: "Execute policy", + Description: "Prepare and compile policy bundles, and execute them using the prepared SSH configuration data", + Steps: steps, + }) + results, err := policyManager.New(ctx, l.logger, policyPath).Execute(ctx, "compliance_plugin", map[string]interface{}{ + "SecurityGroupID": *group.ID, + "Location": *group.Location, + "Name": *group.Name, + "Properties": properties, + "Tags": tags, + "Type": *group.Type, + "DefaultSecurityRules": group.Properties.DefaultSecurityRules, + "SecurityRules": group.Properties.SecurityRules, + "ProvisioningState": group.Properties.ProvisioningState, + }) + if err != nil { + l.logger.Error("policy evaluation failed", "error", err) + evalStatus = proto.ExecutionStatus_FAILURE + accumulatedErrors = errors.Join(accumulatedErrors, err) + continue } - // There are violations in the policy checks. - // We'll send these observations back to the agent - if len(result.Violations) > 0 { - title := fmt.Sprintf("The plugin found violations for policy %s on machineId: %s", result.Policy.Package.PurePackage(), "ID:12345") - observationUuid := uuid.New().String() - assessmentResult.AddObservation(&proto.Observation{ - Uuid: observationUuid, - Title: &title, - Description: fmt.Sprintf("Observed %d violation(s) for policy %s", len(result.Violations), result.Policy.Package.PurePackage()), - Collected: timestamppb.New(time.Now()), - Expires: timestamppb.New(time.Now().AddDate(0, 1, 0)), // Add one month for the expiration + for _, result := range results { + // Observation UUID should differ for each individual subject, but remain consistent when validating the same policy for the same subject. + // This acts as an identifier to show the history of an observation. + observationUUIDMap := internal.MergeMaps(subjectAttributeMap, map[string]string{ + "type": "observation", + "policy": result.Policy.Package.PurePackage(), + "policy_file": result.Policy.File, + "policy_path": policyPath, + }) + observationUUID, err := sdk.SeededUUID(observationUUIDMap) + if err != nil { + accumulatedErrors = errors.Join(accumulatedErrors, err) + // We've been unable to do much here, but let's try the next one regardless. + continue + } + + // Finding UUID should differ for each individual subject, but remain consistent when validating the same policy for the same subject. + // This acts as an identifier to show the history of a finding. + findingUUIDMap := internal.MergeMaps(subjectAttributeMap, map[string]string{ + "type": "finding", + "policy": result.Policy.Package.PurePackage(), + "policy_file": result.Policy.File, + "policy_path": policyPath, + }) + findingUUID, err := sdk.SeededUUID(findingUUIDMap) + if err != nil { + accumulatedErrors = errors.Join(accumulatedErrors, err) + // We've been unable to do much here, but let's try the next one regardless. + continue + } + + observation := proto.Observation{ + ID: uuid.New().String(), + UUID: observationUUID.String(), + Collected: timestamppb.New(startTime), + Expires: timestamppb.New(startTime.Add(24 * time.Hour)), + Origins: []*proto.Origin{{Actors: actors}}, + Subjects: subjects, + Activities: activities, + Components: components, RelevantEvidence: []*proto.RelevantEvidence{ { - Description: fmt.Sprintf("Policy %v was evaluated, and %d violations were found", result.Policy.Package.PurePackage(), len(result.Violations)), + Description: fmt.Sprintf("Policy %v was executed against the Azure Security Group configuration, using the Azure Security Group Compliance Plugin", result.Policy.Package.PurePackage()), }, }, - Labels: map[string]string{ - "package": string(result.Policy.Package), - "type": "azure", - "service": "security-groups", - "security_group_id": fmt.Sprintf("%v", instance["SecurityGroupID"]), - }, - }) + } - for _, violation := range result.Violations { - status := runner.FindingTargetStatusNotSatisfied - assessmentResult.AddFinding(&proto.Finding{ - Title: violation.Title, - Description: violation.Description, - Remarks: &violation.Remarks, - RelatedObservations: []*proto.RelatedObservation{ - { - ObservationUuid: observationUuid, - }, - }, - Target: &proto.FindingTarget{ - Status: &proto.ObjectiveStatus{ - State: status, - }, - }, + newFinding := func() *proto.Finding { + return &proto.Finding{ + ID: uuid.New().String(), + UUID: findingUUID.String(), + Collected: timestamppb.New(time.Now()), Labels: map[string]string{ - "package": string(result.Policy.Package), - "type": "azure", - "service": "security-groups", - "security_group_id": fmt.Sprintf("%v", instance["SecurityGroupID"]), + "type": "azure", + "service": "security-groups", + "instance-id": *group.ID, + "instance-name": *group.Name, + "_policy": result.Policy.Package.PurePackage(), + "_policy_path": result.Policy.File, }, - }) + Origins: []*proto.Origin{{Actors: actors}}, + Subjects: subjects, + Components: components, + RelatedObservations: []*proto.RelatedObservation{{ObservationUUID: observation.ID}}, + Controls: nil, + } } - } - for _, risk := range result.Risks { - links := []*proto.Link{} - for _, link := range risk.Links { - links = append(links, &proto.Link{ - Href: link.URL, - Text: &link.Text, - }) + // There are no violations reported from the policies. + // We'll send the observation back to the agent + if len(result.Violations) == 0 { + + observation.Title = internal.StringAddressed("The plugin succeeded. No compliance issues to report.") + observation.Description = "The plugin policies did not return any violations. The configuration is in compliance with policies." + observations = append(observations, &observation) + + finding := newFinding() + finding.Title = fmt.Sprintf("No violations found on %s", result.Policy.Package.PurePackage()) + finding.Description = fmt.Sprintf("No violations were found on the %s policy within the Azure Security Groups Compliance Plugin.", result.Policy.Package.PurePackage()) + finding.Status = &proto.FindingStatus{ + State: runner.FindingTargetStatusSatisfied, + } + findings = append(findings, finding) + continue } - assessmentResult.AddRiskEntry(&proto.Risk{ - Title: risk.Title, - Description: risk.Description, - Statement: risk.Statement, - Props: []*proto.Property{}, - Links: links, - }) + // There are violations in the policy checks. + // We'll send these observations back to the agent + if len(result.Violations) > 0 { + observation.Title = internal.StringAddressed(fmt.Sprintf("Validation on %s failed.", result.Policy.Package.PurePackage())) + observation.Description = fmt.Sprintf("Observed %d violation(s) on the %s policy within the Azure Security groups Compliance Plugin.", len(result.Violations), result.Policy.Package.PurePackage()) + observations = append(observations, &observation) + + for _, violation := range result.Violations { + finding := newFinding() + finding.Title = violation.Title + finding.Description = violation.Description + finding.Remarks = internal.StringAddressed(violation.Remarks) + finding.Status = &proto.FindingStatus{ + State: runner.FindingTargetStatusNotSatisfied, + } + findings = append(findings, finding) + } + } } - } - - assessmentResult.Start = timestamppb.New(startTime) - - var endTime = time.Now() - assessmentResult.End = timestamppb.New(endTime) - streamId, err := sdk.SeededUUID(map[string]string{ - "type": "azure", - "_policy": policyPath, - "security_group_id": fmt.Sprintf("%v", instance["SecurityGroupID"]), - }) - if err != nil { - l.logger.Error("Failed to seedUUID", "error", err) - evalStatus = proto.ExecutionStatus_FAILURE - errAcc = errors.Join(errAcc, err) - continue + } + if err = apiHelper.CreateObservations(ctx, observations); err != nil { + l.logger.Error("Failed to send observations", "error", err) + return &proto.EvalResponse{ + Status: proto.ExecutionStatus_FAILURE, + }, err } - assessmentResult.AddLogEntry(&proto.AssessmentLog_Entry{ - Title: protolang.String("Template check"), - Description: protolang.String("Template plugin checks completed successfully"), - Start: timestamppb.New(startTime), - End: timestamppb.New(endTime), - }) - - err = apiHelper.CreateResult( - streamId.String(), - map[string]string{ - "type": "azure", - "service": "security-groups", - "_policy": policyPath, - "security_group_id": fmt.Sprintf("%v", instance["SecurityGroupID"]), - }, - policyPath, - assessmentResult.Result()) - if err != nil { - l.logger.Error("Failed to add assessment result", "error", err) - evalStatus = proto.ExecutionStatus_FAILURE - errAcc = errors.Join(errAcc, err) + if err = apiHelper.CreateFindings(ctx, findings); err != nil { + l.logger.Error("Failed to send findings", "error", err) + return &proto.EvalResponse{ + Status: proto.ExecutionStatus_FAILURE, + }, err } + } } + l.logger.Debug("evaluating data", securityGroups) return &proto.EvalResponse{ Status: evalStatus, - }, errAcc + }, accumulatedErrors } func main() {