Skip to content

Commit 86dd4ec

Browse files
authored
feat(policy): Add Rego policy engine package (#1085)
Signed-off-by: Jose I. Paris <[email protected]>
1 parent 5db8d09 commit 86dd4ec

File tree

8 files changed

+320
-0
lines changed

8 files changed

+320
-0
lines changed

go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ require (
7777
github.com/invopop/jsonschema v0.7.0
7878
github.com/jackc/pgx/v5 v5.5.4
7979
github.com/muesli/reflow v0.3.0
80+
github.com/open-policy-agent/opa v0.63.0
8081
github.com/openvex/go-vex v0.2.5
8182
github.com/posthog/posthog-go v0.0.0-20240327112532-87b23fe11103
8283
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
@@ -102,6 +103,8 @@ require (
102103
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect
103104
github.com/Masterminds/semver/v3 v3.2.1 // indirect
104105
github.com/Microsoft/hcsshim v0.11.4 // indirect
106+
github.com/OneOfOne/xxhash v1.2.8 // indirect
107+
github.com/agnivade/levenshtein v1.1.1 // indirect
105108
github.com/anchore/go-struct-converter v0.0.0-20230627203149-c72ef8859ca9 // indirect
106109
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
107110
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect
@@ -120,11 +123,13 @@ require (
120123
github.com/gliderlabs/ssh v0.3.6 // indirect
121124
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
122125
github.com/go-git/go-billy/v5 v5.5.0 // indirect
126+
github.com/go-ini/ini v1.67.0 // indirect
123127
github.com/go-jose/go-jose/v4 v4.0.1 // indirect
124128
github.com/go-ole/go-ole v1.2.6 // indirect
125129
github.com/go-playground/assert/v2 v2.2.0 // indirect
126130
github.com/go-sql-driver/mysql v1.8.1 // indirect
127131
github.com/goadesign/goa v2.2.5+incompatible // indirect
132+
github.com/gobwas/glob v0.2.3 // indirect
128133
github.com/gofrs/uuid v4.4.0+incompatible // indirect
129134
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
130135
github.com/google/cel-go v0.20.1 // indirect
@@ -164,6 +169,7 @@ require (
164169
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
165170
github.com/pkg/xattr v0.4.9 // indirect
166171
github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect
172+
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
167173
github.com/rs/xid v1.5.0 // indirect
168174
github.com/sagikazarmark/locafero v0.4.0 // indirect
169175
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
@@ -175,14 +181,19 @@ require (
175181
github.com/sourcegraph/conc v0.3.0 // indirect
176182
github.com/spiffe/go-spiffe/v2 v2.2.0 // indirect
177183
github.com/stoewer/go-strcase v1.3.0 // indirect
184+
github.com/tchap/go-patricia/v2 v2.3.1 // indirect
178185
github.com/tklauser/go-sysconf v0.3.12 // indirect
179186
github.com/tklauser/numcpus v0.6.1 // indirect
180187
github.com/xanzy/ssh-agent v0.3.3 // indirect
188+
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
189+
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
190+
github.com/yashtewari/glob-intersection v0.2.0 // indirect
181191
github.com/yusufpapurcu/wmi v1.2.3 // indirect
182192
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
183193
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
184194
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 // indirect
185195
go.opentelemetry.io/otel/metric v1.24.0 // indirect
196+
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
186197
go.step.sm/crypto v0.44.2 // indirect
187198
goa.design/goa v2.2.5+incompatible // indirect
188199
gopkg.in/go-jose/go-jose.v2 v2.6.3 // indirect

go.sum

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,8 @@ github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:o
206206
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
207207
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
208208
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
209+
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
210+
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
209211
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
210212
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
211213
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
@@ -277,6 +279,8 @@ github.com/buildkite/go-pipeline v0.3.2/go.mod h1:iY5jzs3Afc8yHg6KDUcu3EJVkfaUkd
277279
github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 h1:k6UDF1uPYOs0iy1HPeotNa155qXRWrzKnqAaGXHLZCE=
278280
github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251/go.mod h1:gbPR1gPu9dB96mucYIR7T3B7p/78hRVSOuzIWLHK2Y4=
279281
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
282+
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 h1:3uZCA/BLTIu+DqCfguByNMJa2HVHpXvjfy0Dy7g6fuA=
283+
github.com/bytecodealliance/wasmtime-go/v3 v3.0.2/go.mod h1:RnUjnIXxEJcL6BgCvNyzCCRzZcxCgsZCi+RNlvYor5Q=
280284
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
281285
github.com/casbin/casbin/v2 v2.29.2/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg=
282286
github.com/casbin/casbin/v2 v2.81.0 h1:vNwJXK7a+TJZElZ5saP+SFJvweZNtJ3MlVP6P4IuRqE=
@@ -295,6 +299,7 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
295299
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
296300
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
297301
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
302+
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
298303
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
299304
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
300305
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@@ -378,8 +383,14 @@ github.com/denisbrodbeck/machineid v1.0.1 h1:geKr9qtkB876mXguW2X6TU4ZynleN6ezuMS
378383
github.com/denisbrodbeck/machineid v1.0.1/go.mod h1:dJUwb7PTidGDeYyUBmXZ2GphQBbjJCrnectwCyxcUSI=
379384
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I=
380385
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE=
386+
github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg=
387+
github.com/dgraph-io/badger/v3 v3.2103.5/go.mod h1:4MPiseMeDQ3FNCYwRbbcBOGJLf5jsE0PPFzRiKjtcdw=
388+
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
389+
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
381390
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
382391
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
392+
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
393+
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
383394
github.com/digitorus/pkcs7 v0.0.0-20230713084857-e76b763bdc49/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc=
384395
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 h1:ge14PCmCvPjpMQMIAH7uKg0lrtNSOdpYsRXlwk3QbaE=
385396
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352/go.mod h1:SKVExuS+vpu2l9IoOc0RwqE7NYnb0JlcFHFnEJkVDzc=
@@ -440,6 +451,10 @@ github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4Nij
440451
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
441452
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
442453
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
454+
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
455+
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
456+
github.com/foxcpp/go-mockdns v1.1.0 h1:jI0rD8M0wuYAxL7r/ynTrCQQq0BVqfB99Vgk7DlmewI=
457+
github.com/foxcpp/go-mockdns v1.1.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk=
443458
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
444459
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
445460
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
@@ -573,6 +588,8 @@ github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w
573588
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
574589
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
575590
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
591+
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
592+
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
576593
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
577594
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
578595
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -620,6 +637,8 @@ github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
620637
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
621638
github.com/google/certificate-transparency-go v1.1.8 h1:LGYKkgZF7satzgTak9R4yzfJXEeYVAjV6/EAEJOf1to=
622639
github.com/google/certificate-transparency-go v1.1.8/go.mod h1:bV/o8r0TBKRf1X//iiiSgWrvII4d7/8OiA+3vG26gI8=
640+
github.com/google/flatbuffers v2.0.8+incompatible h1:ivUb1cGomAB101ZM1T0nOiWz9pSrTMoa9+EiY7igmkM=
641+
github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
623642
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49 h1:0VpGH+cDhbDtdcweoyCVsF3fhN8kejK6rFe/2FFX2nU=
624643
github.com/google/gnostic-models v0.6.9-0.20230804172637-c7be7c783f49/go.mod h1:BkkQ4L1KS1xMt2aWSPStnn55ChGC0DPOn2FQYj+f25M=
625644
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -1005,6 +1024,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
10051024
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
10061025
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
10071026
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
1027+
github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM=
1028+
github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk=
10081029
github.com/miekg/pkcs11 v1.0.3-0.20190429190417-a667d056470f/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
10091030
github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU=
10101031
github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
@@ -1461,6 +1482,8 @@ go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
14611482
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
14621483
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4=
14631484
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0/go.mod h1:noq80iT8rrHP1SfybmPiRGc9dc5M8RPmGvtwo7Oo7tc=
1485+
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0 h1:tIqheXEFWAZ7O8A7m+J0aPTmpJN3YQ7qetUAdkkkKpk=
1486+
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.21.0/go.mod h1:nUeKExfxAQVbiVFn32YXpXZZHZ61Cc3s3Rn1pDBGAb0=
14641487
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0 h1:FyjCyI9jVEfqhUh2MoSkmolPjfh5fp2hnV0b0irxH4Q=
14651488
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.22.0/go.mod h1:hYwym2nDEeZfG/motx0p7L7J1N1vyzIThemQsb4g2qY=
14661489
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=

pkg/policies/engine/engine.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// Copyright 2024 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package engine
17+
18+
import (
19+
"context"
20+
)
21+
22+
type PolicyEngine interface {
23+
// Verify verifies an input against a policy
24+
Verify(ctx context.Context, policy *Policy, input []byte) ([]*PolicyViolation, error)
25+
}
26+
27+
// PolicyViolation represents a policy failure
28+
type PolicyViolation struct {
29+
Subject, Violation string
30+
}
31+
32+
// Policy represents a loaded policy in any of the supported technologies.
33+
type Policy struct {
34+
// the source code for this policy
35+
Source []byte `json:"module"`
36+
// The unique policy name
37+
Name string `json:"name"`
38+
}

pkg/policies/engine/rego/rego.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//
2+
// Copyright 2024 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package rego
17+
18+
import (
19+
"bytes"
20+
"context"
21+
"encoding/json"
22+
"errors"
23+
"fmt"
24+
25+
"github.com/chainloop-dev/chainloop/pkg/policies/engine"
26+
"github.com/open-policy-agent/opa/ast"
27+
"github.com/open-policy-agent/opa/rego"
28+
)
29+
30+
// Rego policy checker for chainloop attestations and materials
31+
type Rego struct {
32+
}
33+
34+
// Force interface
35+
var _ engine.PolicyEngine = (*Rego)(nil)
36+
37+
func (r *Rego) Verify(ctx context.Context, policy *engine.Policy, input []byte) ([]*engine.PolicyViolation, error) {
38+
policyString := string(policy.Source)
39+
parsedModule, err := ast.ParseModule(policy.Name, policyString)
40+
if err != nil {
41+
return nil, fmt.Errorf("failed to parse rego policy: %w", err)
42+
}
43+
44+
// Decode input as json
45+
decoder := json.NewDecoder(bytes.NewReader(input))
46+
decoder.UseNumber()
47+
var decodedInput interface{}
48+
if err := decoder.Decode(&decodedInput); err != nil {
49+
return nil, fmt.Errorf("failed to parse input: %w", err)
50+
}
51+
52+
// add input
53+
regoInput := rego.Input(decodedInput)
54+
55+
// add module
56+
regoFunc := rego.ParsedModule(parsedModule)
57+
58+
// add query. Note that the predefined rule to look for is `deny`
59+
query := rego.Query(fmt.Sprintf("%v.deny\n", parsedModule.Package.Path))
60+
61+
regoEval := rego.New(regoInput, regoFunc, query)
62+
63+
res, err := regoEval.Eval(ctx)
64+
if err != nil {
65+
return nil, fmt.Errorf("failed to evaluate policy: %w", err)
66+
}
67+
68+
// If res is nil, it means that the rule hasn't been found
69+
if res == nil {
70+
return nil, errors.New("failed to evaluate policy: no 'deny' rule found")
71+
}
72+
73+
violations := make([]*engine.PolicyViolation, 0)
74+
for _, exp := range res {
75+
for _, val := range exp.Expressions {
76+
denyReasons, ok := val.Value.([]interface{})
77+
if !ok {
78+
return nil, fmt.Errorf("failed to evaluate policy expression evaluation result: %s", val.Text)
79+
}
80+
81+
for _, reason := range denyReasons {
82+
reasonStr, ok := reason.(string)
83+
if !ok {
84+
return nil, fmt.Errorf("failed to evaluate deny reason: %s", val.Text)
85+
}
86+
87+
violations = append(violations, &engine.PolicyViolation{
88+
Subject: policy.Name,
89+
Violation: reasonStr,
90+
})
91+
}
92+
}
93+
}
94+
95+
return violations, nil
96+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//
2+
// Copyright 2024 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package rego
17+
18+
import (
19+
"context"
20+
"os"
21+
"testing"
22+
23+
"github.com/chainloop-dev/chainloop/pkg/policies/engine"
24+
"github.com/stretchr/testify/assert"
25+
"github.com/stretchr/testify/require"
26+
)
27+
28+
func TestRego_VerifyWithValidPolicy(t *testing.T) {
29+
regoContent, err := os.ReadFile("testfiles/check_qa.rego")
30+
require.NoError(t, err)
31+
32+
r := &Rego{}
33+
policy := &engine.Policy{
34+
Name: "check approval",
35+
Source: regoContent,
36+
}
37+
38+
t.Run("invalid input", func(t *testing.T) {
39+
violations, err := r.Verify(context.TODO(), policy, []byte("{\"foo\": \"bar\"}"))
40+
require.NoError(t, err)
41+
assert.Len(t, violations, 2)
42+
assert.Contains(t, violations, &engine.PolicyViolation{
43+
Subject: policy.Name,
44+
Violation: "Container image is not approved",
45+
})
46+
assert.Contains(t, violations, &engine.PolicyViolation{
47+
Subject: policy.Name,
48+
Violation: "Container image is not released",
49+
})
50+
})
51+
52+
t.Run("valid input", func(t *testing.T) {
53+
violations, err := r.Verify(context.TODO(), policy, []byte(`
54+
{
55+
"kind": "CONTAINER_IMAGE",
56+
"references": [{
57+
"metadata": {"name": "chainloop-platform-qa-approval"},
58+
"annotations": {"approval": "true"}
59+
}, {
60+
"metadata": {"name": "chainloop-platform-release-production"}
61+
}]
62+
}`))
63+
require.NoError(t, err)
64+
assert.Len(t, violations, 0)
65+
})
66+
}
67+
68+
func TestRego_VerifyInvalidPolicy(t *testing.T) {
69+
// load policy without a default deny rule
70+
regoContent, err := os.ReadFile("testfiles/policy_without_deny.rego")
71+
require.NoError(t, err)
72+
73+
r := &Rego{}
74+
policy := &engine.Policy{
75+
Name: "invalid",
76+
Source: regoContent,
77+
}
78+
79+
t.Run("doesn't eval a deny rule", func(t *testing.T) {
80+
violations, err := r.Verify(context.TODO(), policy, []byte("{\"foo\": \"bar\"}"))
81+
assert.Error(t, err)
82+
assert.Len(t, violations, 0)
83+
})
84+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package main
2+
3+
deny[msg] {
4+
not is_released
5+
6+
msg:= "Container image is not released"
7+
}
8+
9+
deny[msg] {
10+
not is_approved
11+
12+
msg:= "Container image is not approved"
13+
}
14+
15+
is_approved {
16+
input.kind == "CONTAINER_IMAGE"
17+
18+
input.references[i].metadata.name == "chainloop-platform-qa-approval"
19+
input.references[i].annotations.approval == "true"
20+
}
21+
22+
is_released {
23+
input.kind == "CONTAINER_IMAGE"
24+
25+
input.references[i].metadata.name == "chainloop-platform-release-production"
26+
}

0 commit comments

Comments
 (0)