Skip to content
Snippets Groups Projects
Commit d50eef4e authored by Kunz, Immanuel's avatar Kunz, Immanuel
Browse files

final iteration of the life cycle manager

parent d07f85f9
No related branches found
No related tags found
No related merge requests found
......@@ -36,6 +36,8 @@ import (
"net/http"
"net/url"
"os"
"strconv"
"strings"
"time"
"clouditor.io/clouditor/api/orchestrator"
......@@ -46,6 +48,35 @@ import (
log "github.com/sirupsen/logrus"
)
const (
OperationalEffectivenessInterval = "OPERATIONAL_EFFECTIVENESS_INTERVAL"
DefaultOperationalEffectivenessInterval = "720"
OperationalEffectivenessThreshold = "OPERATIONAL_EFFECTIVENESS_THRESHOLD"
DefaultOperationalEffectivenessThreshold = "50"
)
var (
operationalEffectivenessInterval string
operationalEffectivenessThreshold string
)
func init() {
var (
ok bool
)
operationalEffectivenessInterval, ok = os.LookupEnv(OperationalEffectivenessInterval)
if !ok {
operationalEffectivenessInterval = DefaultOperationalEffectivenessInterval
}
log.Info("OE Interval set to", operationalEffectivenessInterval)
operationalEffectivenessThreshold, ok = os.LookupEnv(OperationalEffectivenessThreshold)
if !ok {
operationalEffectivenessThreshold = DefaultOperationalEffectivenessThreshold
}
log.Info("OE threshold set to", operationalEffectivenessThreshold)
}
type CreateCertificateRequest struct {
Certificate orchestrator.Certificate
}
......@@ -86,7 +117,6 @@ func GetCert(certificateId string) (certificate orchestrator.Certificate, err er
return orchCertificate, err
}
// using gRPC here now; the plan is to gradually move to gRPC where possible
func ListCerts() (certs []*orchestrator.Certificate, err error) {
listCertificatesRequest := orchestrator.ListCertificatesRequest{}
listCertificatesResponse, err := orchestratorClient.ListCertificates(context.Background(), &listCertificatesRequest)
......@@ -114,56 +144,118 @@ func ListToEs() (services []*orchestrator.TargetOfEvaluation, err error) {
return listTargetsOfEvaluationResponse.TargetOfEvaluation, nil
}
func CreateCert(cert orchestrator.Certificate) (status string, err error) {
reqBody, err := protojson.Marshal(&cert)
if err != nil {
fmt.Printf("json marshalling failed:")
return "500", err
func CreateCert(cert orchestrator.Certificate) (err error) {
req := orchestrator.CreateCertificateRequest{
Certificate: &cert,
}
res, err := ExecuteHttpRequest("POST", fmt.Sprintf("http://%v/v1/orchestrator/certificates", orchestratorUrl), reqBody)
_, err = orchestratorClient.CreateCertificate(context.Background(), &req)
if err != nil {
log.Errorf("Error %v", err)
return
}
return res.Status, err
createCABCert(cert)
return err
}
func UpdateCertStatus(newstate string, certId string) (err error) {
func UpdateCertStatus(newstate string, toeId string, treeId string) (err error) {
// list certificates to find the one for this toe; to be discussed if we should change the API in Clouditor to allow direct query for certificate via ToE
certificateList, err := ListCerts()
if err != nil {
fmt.Printf("could not list certificates")
return err
}
var certificateId string
for _, v := range certificateList {
// fmt.Println("Checking toe IDs ", v.CloudServiceId+v.Standard, toeId)
if v.CloudServiceId == strings.Split(toeId, "EUCS")[0] {
// if v.CloudServiceId+v.Standard == toeId {
certificateId = v.Id
fmt.Println("found certificate for toe id with the cert id:", v.Id)
}
}
if certificateId == "" {
return err
}
// get the certificate from the DB
certificate, err := GetCert(certId)
certificate, err := GetCert(certificateId)
if err != nil {
fmt.Printf("could not find certificate")
return err
}
loc, _ := time.LoadLocation("Europe/Berlin")
// update history
// TODO implement update of tree id
state := &orchestrator.State{State: newstate, TreeId: "1234", Timestamp: time.Now().String(), CertificateId: certId, Id: uuid.NewString()}
state := &orchestrator.State{
State: newstate,
TreeId: treeId,
Timestamp: time.Now().In(loc).Format(time.RFC822),
CertificateId: certificateId,
Id: uuid.NewString(),
}
certificate.States = append(certificate.States, state)
fmt.Println("Sending certificate:", certificate)
fmt.Println("To:", fmt.Sprintf("http://%v/v1/orchestrator/certificates/"+certificateId, orchestratorUrl))
// send update to Clouditor Orchestrator
reqBody, err := protojson.Marshal(&certificate)
if err != nil {
fmt.Printf("json marshalling failed:")
return err
}
_, err = ExecuteHttpRequest("PUT", fmt.Sprintf("http://%v/v1/orchestrator/certificates/"+certId, orchestratorUrl), reqBody)
_, err = ExecuteHttpRequest("PUT", fmt.Sprintf("http://%v/v1/orchestrator/certificates/"+certificateId, orchestratorUrl), reqBody)
if err != nil {
return
}
// report to SSI/CAB if cert is suspended or withdrawn, so it can be reviewed by an auditor manually
if newstate == "withdrawn" || newstate == "suspended" {
ReportToCAB(certId, newstate)
ReportToCAB(certificateId, newstate)
}
return
}
func createCABCert(cert orchestrator.Certificate) (err error) {
log.Info("creating new cert in CAB / SSI system")
url, _ := url.Parse(fmt.Sprintf("%v/certificates", ssiUrl))
urlValues := url.Query()
urlValues.Add("certificate_id", cert.Id)
urlValues.Add("certificate_status", "new")
url.RawQuery = urlValues.Encode()
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{},
},
}
req, err := http.NewRequest("POST", url.String(), nil)
if err != nil {
fmt.Printf("creating http request failed")
}
ssiApiToken, ok := os.LookupEnv("SSI_API_TOKEN")
if !ok {
ssiApiToken = "ssiApiToken"
}
req.Header.Add("x-auth", ssiApiToken)
fmt.Println("sending request to SSI:", req)
res, err := client.Do(req)
if err != nil {
log.Errorf("Error while reporting certificate state to SSI system: ", err)
}
fmt.Println("Got response from SSI system: ", res)
return err
}
func ReportToCAB(certId, newstate string) (err error) {
log.Info("reporting cert status to CAB / SSI system")
url, _ := url.Parse(fmt.Sprintf("%v/certificates", ssiUrl))
url, _ := url.Parse(fmt.Sprintf("%v/certificate/id", ssiUrl))
urlValues := url.Query()
urlValues.Add("certificate_id", certId)
urlValues.Add("certificate_status", newstate)
......@@ -181,13 +273,14 @@ func ReportToCAB(certId, newstate string) (err error) {
}
ssiApiToken, ok := os.LookupEnv("SSI_API_TOKEN")
if !ok {
ssiApiToken = "ssiApiToken"
ssiApiToken = "8744834hdhsjmn"
}
req.Header.Add("x-auth", ssiApiToken)
fmt.Println("sending request to SSI:", req)
res, err := client.Do(req)
if err != nil {
log.Errorf("Error while reporting certificate state to SSI system")
log.Errorf("Error while reporting certificate state to SSI system: ", err)
}
fmt.Println("Got response from SSI system: ", res)
......@@ -195,10 +288,6 @@ func ReportToCAB(certId, newstate string) (err error) {
}
func UpdateCertInfo(cert orchestrator.Certificate) (err error) {
// get the certificate from the DB
// certificate, err := GetCert(cert.ID)
// TODO check if all (relevant) fields are filled
// update in Clouditor Orchestrator
reqBody, err := protojson.Marshal(&cert)
if err != nil {
......@@ -211,7 +300,18 @@ func UpdateCertInfo(cert orchestrator.Certificate) (err error) {
}
func DeleteCert(certificateId string) (err error) {
_, err = ExecuteHttpRequest("DELETE", fmt.Sprintf("http://%v/v1/orchestrator/certificates/"+certificateId, orchestratorUrl), nil)
deleteCertificateRequest := &orchestrator.RemoveCertificateRequest{
CertificateId: certificateId,
}
reqBody, err := protojson.Marshal(deleteCertificateRequest)
if err != nil {
fmt.Printf("json marshalling failed:")
}
_, err = ExecuteHttpRequest("DELETE", fmt.Sprintf("http://%v/v1/orchestrator/certificates/"+certificateId, orchestratorUrl), reqBody)
if err != nil {
fmt.Println("Error when deleting certificate:", err)
}
return err
}
......@@ -233,8 +333,7 @@ type Requirement struct {
}
// this function calls the CCE component to get statistics about requirement compliance
func ExecuteOperationalEffectivenessMeasurement() (err error) {
for {
func ExecuteOperationalEffectivenessMeasurement() {
// First, get the existing ToEs, then get operational effectiveness data for them
toes, err := ListToEs()
if err != nil {
......@@ -246,25 +345,26 @@ func ExecuteOperationalEffectivenessMeasurement() (err error) {
log.Info("Retrieving OE data...")
GetOperationalEffectiveness(*toe)
}
time.Sleep(time.Hour * 24)
}
}
func GetOperationalEffectiveness(toe orchestrator.TargetOfEvaluation) (err error) {
// Operational effectiveness should be checked periodically, e.g. once a day
for {
func GetOperationalEffectiveness(toe orchestrator.TargetOfEvaluation) {
url, _ := url.Parse(fmt.Sprintf("http://%v/toes/%v:%v/statistics", cceUrl, toe.CloudServiceId, toe.CatalogId))
urlValues := url.Query()
urlValues.Add("end", time.Now().Format("2000-10-31T01:30:00.000-05:00"))
urlValues.Add("start", time.Now().Add(-time.Hour*720).Format("2000-10-31T01:30:00.000-05:00"))
// defining the "end" is optional
// urlValues.Add("end", time.Now().Format("2022-12-13T11:32:33.136Z"))
oeIntervalInt, err := strconv.Atoi(operationalEffectivenessInterval)
if err != nil {
log.Error("could not parse the operational effectiveness interval")
return
}
urlValues.Add("start", time.Now().Add(-time.Hour*time.Duration(oeIntervalInt)).Format(time.RFC3339))
url.RawQuery = urlValues.Encode()
res, err := ExecuteHttpRequest("GET", url.String(), nil)
if err != nil {
log.Error("Error executing HTTP request to get OE data:", err)
continue
return
}
// defer res.Body.Close()
......@@ -272,55 +372,58 @@ func GetOperationalEffectiveness(toe orchestrator.TargetOfEvaluation) (err error
err = json.NewDecoder(res.Body).Decode(operationalEffectiveness)
if err != nil {
log.Error("Error decoding OE data:", err)
continue
return
}
// loop through the RequirementStats and check their OE
for _, s := range operationalEffectiveness.RequirementsStats {
// if the compliance ration is below the threshold, suspend the certificate; TODO make this value adjustable in the env vars
if s.ComplianceRatio < 70 {
fmt.Println("suspending certificate due to bad compliance ratio")
// TODO suspend certificate; this is not possible yet, since we do not have certificate ids integrated yet
// if the compliance ration is below the threshold, suspend the certificate
oeThresholdInt, err := strconv.Atoi(operationalEffectivenessThreshold)
if err != nil {
log.Error("could not parse the operational effectiveness threshold")
return
}
if s.ComplianceRatio < oeThresholdInt {
fmt.Println("suspending certificate due to bad compliance ratio")
UpdateCertStatus("suspended", toe.CloudServiceId+toe.CatalogId, "suspended due to low operational effectiveness")
}
}
}
func SuspendStaleCertificates() {
// This call should be executed periodically, e.g. once a day
for {
// list existing certificates
certificates, err := ListCerts()
if err != nil {
fmt.Printf("could not list certificates")
return
}
fmt.Printf("Found %v certificates", len(certificates))
fmt.Printf("Found %v certificates \n", len(certificates))
// loop through all certificates
for _, cert := range certificates {
// parse timestamp
const form = "2006-01-02T15:04:05Z07:00"
// A default/testing certificate may not have any states
if len(cert.States) == 0 {
continue
}
lastCertUpdate := cert.States[len(cert.States)-1]
// TODO or is it cert.States[0]?
certTime, err := time.Parse(form, lastCertUpdate.Timestamp)
certTime, err := time.Parse(time.RFC822, lastCertUpdate.Timestamp)
if err != nil {
fmt.Printf("could not parse certificate's timestamp")
fmt.Println("could not parse certificate's timestamp")
continue
}
loc, _ := time.LoadLocation("Europe/Berlin")
// suspend if not updated in the last 3 months
if certTime.Add(2184 * time.Hour).Before(time.Now()) {
if (lastCertUpdate.State != "continued") && (lastCertUpdate.State != "new") && certTime.Add(2184*time.Hour).Before(time.Now()) {
fmt.Printf("certTime", certTime, "certTime + 3 months", certTime.Add(2184*time.Hour), "time.Now()", time.Now().In(loc))
// if the certificate is already suspended, it should be withdrawn
if lastCertUpdate.State == "suspended" {
UpdateCertStatus("withdrawn", cert.Id)
if lastCertUpdate.State == "withdrawn" {
continue
} else if lastCertUpdate.State == "suspended" {
UpdateCertStatus("withdrawn", cert.CloudServiceId+cert.Standard, "withdrawn due to missing remediation")
} else {
UpdateCertStatus("suspended", cert.Id)
}
UpdateCertStatus("suspended", cert.CloudServiceId+cert.Standard, "suspended due to missing evaluations")
}
}
// TODO make this value adjustable in the env vars
time.Sleep(time.Hour * 24)
}
}
......@@ -117,7 +117,7 @@ func init() {
config.TokenURL = fmt.Sprintf("http://%v/v1/auth/token", orchestratorUrl)
}
token, err = config.Token(context.Background())
fmt.Println("Token: ", token)
if err != nil {
log.Errorf("Error while retrieving a token: %v", err)
}
......@@ -131,14 +131,16 @@ func ExecuteHttpRequest(method string, url string, body []byte) (res *http.Respo
fmt.Printf("creating http request failed")
}
req.Header.Add("Authorization", fmt.Sprintf("Bearer %v", token.AccessToken))
fmt.Printf("Sending %v request to: %v", method, req.URL)
fmt.Printf("Sending %v request to: %v \n", method, req.URL)
client := config.Client(context.Background())
res, err = client.Do(req)
if err != nil {
log.Errorf("Error while %v: %v", method, err)
}
} else {
fmt.Printf("Got response for %v HTTP request to %v: %v", method, url, res.Status)
// TODO if response not 200 return err
}
return res, err
}
......@@ -201,6 +203,8 @@ func DefaultGrpcDialOptions(hostport string, authorizer api.Authorizer, addition
opts = append(opts, grpc.WithPerRPCCredentials(authorizer))
}
opts = append(opts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(8388608)))
// Appply any additional options that we might have
opts = append(opts, additionalOpts...)
......
......@@ -30,10 +30,12 @@ package main
import (
"net/http"
"os"
"time"
"life-cycle-manager/cert"
"life-cycle-manager/rest"
"github.com/go-co-op/gocron"
log "github.com/sirupsen/logrus"
)
......@@ -48,13 +50,44 @@ func main() {
"component": "life-cycle-manager",
}).Info("Set logging")
// spawn separate go routine for the periodic check of stale certificates
go cert.SuspendStaleCertificates()
// create an initial certificate
// mockState := &orchestrator.State{
// CertificateId: "1234",
// TreeId: "12345",
// Timestamp: time.Now().Format("2006-01-02T15:04:05Z07:00"),
// State: "new",
// Id: "1233",
// }
// certificate := &orchestrator.Certificate{
// Id: "1234",
// Name: "EUCS test certificate",
// CloudServiceId: "Bosch",
// IssueDate: "2022-06-01T16:16:55Z",
// ExpirationDate: "2025-06-01T16:16:54Z",
// Standard: "EUCS",
// AssuranceLevel: "High",
// Cab: "CAB 123",
// Description: "A test certificate for demonstration purposes",
// States: []*orchestrator.State{mockState},
// }
// log.Info("creating default certificate")
// cert.CreateCert(*certificate)
// spawn separate go routine for the periodic check of stale certificates
go cert.ExecuteOperationalEffectivenessMeasurement()
s := gocron.NewScheduler(time.UTC)
s.Every(1).Day().Do(suspendTask)
// periodic check of operational effectiveness
s.Every(1).Day().Do(operationalEffectivenessTask)
s.StartAsync()
err = http.ListenAndServe(":8090", rest.NewRouter())
log.Errorf("An error occured: %v", err)
}
func suspendTask() {
cert.SuspendStaleCertificates()
}
func operationalEffectivenessTask() {
cert.ExecuteOperationalEffectivenessMeasurement()
}
module life-cycle-manager
go 1.18
go 1.19
require (
clouditor.io/clouditor v1.7.1
clouditor.io/clouditor v1.9.3
github.com/gin-contrib/logger v0.2.0
github.com/gin-gonic/gin v1.7.7
github.com/google/uuid v1.3.0
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.1
google.golang.org/grpc v1.51.0
github.com/sirupsen/logrus v1.9.3
github.com/stretchr/testify v1.8.3
google.golang.org/grpc v1.57.0-dev.0.20230612212144-642dd63a8527
)
require (
github.com/envoyproxy/protoc-gen-validate v0.1.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.14.0 // indirect
github.com/envoyproxy/protoc-gen-validate v1.0.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/srikrsna/protoc-gen-gotag v0.6.2 // indirect
golang.org/x/text v0.5.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/text v0.11.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20221114212237-e4508ebdbee1 // indirect
google.golang.org/genproto v0.0.0-20230526203410-71b5a4ffd15e // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-co-op/gocron v1.28.0
github.com/go-playground/locales v0.14.0 // indirect
github.com/go-playground/universal-translator v0.18.0 // indirect
github.com/go-playground/validator/v10 v10.11.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/zerolog v1.23.0 // indirect
github.com/ugorji/go/codec v1.2.7 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/net v0.4.0 // indirect
golang.org/x/oauth2 v0.3.0
golang.org/x/sys v0.3.0 // indirect
google.golang.org/protobuf v1.28.1
golang.org/x/crypto v0.11.0 // indirect
golang.org/x/net v0.11.0 // indirect
golang.org/x/oauth2 v0.8.0
golang.org/x/sys v0.10.0 // indirect
google.golang.org/protobuf v1.30.0
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
This diff is collapsed.
......@@ -66,9 +66,14 @@ type CertificateRequest struct {
}
type EvaluationReport struct {
CertificateId string
ToeId string
MajorDeviation bool
Description string
TreeId string
}
type GetRequest struct {
CertificateId string
}
type DeletionRequest struct {
......@@ -92,6 +97,20 @@ func NewErrorResponse(msg string) *ErrorResponse {
return &ErrorResponse{msg}
}
func HandleGetCertificate(c *gin.Context) {
var (
req GetRequest
err error
)
certificate, err := cert.GetCert(req.CertificateId)
if err != nil {
c.JSON(http.StatusBadRequest, NewErrorResponse("Could not retrieve certificate"))
log.Errorf("Error: %v", err)
} else {
c.JSON(http.StatusOK, certificate)
}
}
// create a new certificate
func HandleCreation(c *gin.Context) {
var (
......@@ -102,15 +121,14 @@ func HandleCreation(c *gin.Context) {
if err := c.BindJSON(&certificate); err != nil {
c.JSON(http.StatusBadRequest, NewErrorResponse("invalid request"))
fmt.Println(err)
return
}
res, err := cert.CreateCert(certificate)
err = cert.CreateCert(certificate)
if err != nil {
fmt.Println("error while creating the certificate: ", err)
c.JSON(http.StatusInternalServerError, "Certificate couldn't be created")
} else if res != "200 OK" {
c.JSON(http.StatusInternalServerError, "Error")
} else {
c.JSON(http.StatusCreated, "New certificate created")
}
......@@ -127,14 +145,17 @@ func HandleEvaluation(c *gin.Context) {
c.JSON(http.StatusBadRequest, NewErrorResponse("invalid request"))
fmt.Println(err)
}
fmt.Println("evaluation report", req)
fmt.Println("evaluation report description ", req.Description)
fmt.Println("evaluation report deviaton type ", req.MajorDeviation)
fmt.Println("evaluation report toe id ", req.ToeId)
fmt.Println("evaluation report tree id ", req.TreeId)
// trigger the respective event
if req.MajorDeviation {
cert.UpdateCertStatus("suspended", req.CertificateId)
cert.UpdateCertStatus("suspended", req.ToeId, req.TreeId)
c.JSON(http.StatusOK, "negative_evaluation reported")
} else if !req.MajorDeviation {
cert.UpdateCertStatus("continued", req.CertificateId)
cert.UpdateCertStatus("continued", req.ToeId, req.TreeId)
c.JSON(http.StatusOK, "positive_evaluation reported")
} else {
c.JSON(http.StatusBadRequest, NewErrorResponse("invalid request"))
......@@ -178,10 +199,6 @@ func HandleDeletion(c *gin.Context) {
c.JSON(http.StatusOK, nil)
}
// TODO
// a remediation is necessary to move from suspended to an issued state again; without a remediation, the certificate is withdrawn
// func HandleMissingRemediation(c *gin.Context) {}
func GetStateLog(c *gin.Context) {
var (
req StateLogRequest
......
......@@ -37,11 +37,11 @@ func NewRouter() *gin.Engine {
r.Use(gin.Recovery())
r.Use(logger.SetLogger())
r.POST("/new", HandleCreation)
r.POST("/certificate", HandleCreation)
r.POST("/evaluation", HandleEvaluation)
r.PUT("/update", HandleInfoUpdate)
r.DELETE("/delete", HandleDeletion)
// r.POST("/remediation", HandleMissingRemediation)
r.PUT("/certificate", HandleInfoUpdate)
r.DELETE("/certificate", HandleDeletion)
r.GET("/certificate", HandleGetCertificate)
r.GET("/statechange", GetStateLog)
return r
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment