From aedfcf87a6ebb22c6abd8c86011d2ecea6a738d6 Mon Sep 17 00:00:00 2001 From: Herman Slatman Date: Thu, 29 Jul 2021 01:20:29 +0200 Subject: [PATCH] Refactor and improve logic for signing --- README.md | 4 +- cmd/read.go | 39 ++----- cmd/root.go | 14 +-- cmd/sign.go | 164 ++++++++++++++++++++---------- cmd/validate.go | 45 ++------ cmd/verify.go | 84 ++++++++------- cmd/view.go | 22 +--- go.mod | 1 + internal/json.go | 26 +++++ internal/{signature.go => mud.go} | 33 +++++- internal/read.go | 30 +++++- internal/validate.go | 18 ++++ vendor/modules.txt | 1 + 13 files changed, 284 insertions(+), 197 deletions(-) create mode 100644 internal/json.go rename internal/{signature.go => mud.go} (57%) create mode 100644 internal/validate.go diff --git a/README.md b/README.md index 1ca3c39..a1c2554 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,9 @@ The access control policies described in a MUD File allow network controllers to ## Things that can be done * Fix (most) TODOs ... :-) -* Replace calls to fmt with proper logging +* Add 'Use' to commands +* Replace calls to fmt with proper logging / output +* Allow the tool to be chained (i.e. use STDIN/STDOUT, pipes, etc.) * A command for generating MUD files (from pcap or some different way) * A command for editing MUD files (i.e. metadata) * A command that initializes a .mud directory inside user HOME, that is used for intermediate storage? If necessary, of course. diff --git a/cmd/read.go b/cmd/read.go index 84e6145..64dcdfb 100644 --- a/cmd/read.go +++ b/cmd/read.go @@ -18,51 +18,32 @@ package cmd import ( "fmt" - "github.com/openconfig/ygot/ygot" "github.com/pkg/errors" "github.com/spf13/cobra" "github.com/hslatman/mud-cli/internal" - "github.com/hslatman/mud.yang.go/pkg/mudyang" ) // readCmd represents the read command var readCmd = &cobra.Command{ Use: "read", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Args: cobra.MinimumNArgs(1), + Short: "Reads and prints MUD file contents", + Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - json, err := internal.Contents(args[0]) - if err != nil { - return errors.Wrap(err, "error reading file contents") - } - mud := &mudyang.Mudfile{} - if err := mudyang.Unmarshal(json, mud); err != nil { - return errors.Wrap(err, "can't unmarshal JSON") + filepath := args[0] + mudfile, err := internal.ReadMUDFileFrom(filepath) + if err != nil { + return errors.Wrap(err, "could not get contents") } - // TODO: print a summary instead of the full JSON? - - jsonString, err := ygot.EmitJSON(mud, &ygot.EmitJSONConfig{ - Format: ygot.RFC7951, - Indent: " ", - RFC7951Config: &ygot.RFC7951JSONConfig{ - AppendModuleName: true, - }, - SkipValidation: false, // TODO: provide flag to skip? - }) + json, err := internal.JSON(mudfile) if err != nil { - return errors.Wrap(err, "could not marshal MUD file into JSON") + return errors.Wrap(err, "getting JSON representation of MUD file failed") } - fmt.Println(jsonString) + // TODO: provide ways to show different info? Like a summary? + fmt.Println(json) return nil }, diff --git a/cmd/root.go b/cmd/root.go index a6c3902..b771150 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,7 +16,6 @@ limitations under the License. package cmd import ( - "fmt" "os" "path/filepath" @@ -30,16 +29,7 @@ var mudRootDir string // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "mud", - Short: "A brief description of your application", - Long: `A longer description that spans multiple lines and likely contains -examples and usage of using your application. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, + Short: "mud provides several utilities for working with MUD files", } // Execute adds all child commands to the root command and sets flags appropriately. @@ -70,7 +60,7 @@ func initConfig() { cobra.CheckErr(err) mudRootDir = filepath.Join(home, mudDir) - fmt.Println(mudRootDir) + //fmt.Println(mudRootDir) if !dirExists(mudRootDir) { err = os.MkdirAll(mudRootDir, 0700) // TODO: right permissions? cobra.CheckErr(err) diff --git a/cmd/sign.go b/cmd/sign.go index 313ef68..0109760 100644 --- a/cmd/sign.go +++ b/cmd/sign.go @@ -26,7 +26,9 @@ import ( "io/ioutil" "log" "math/big" + "net/url" fp "path/filepath" + "strings" "time" cms "github.com/github/ietf-cms" @@ -39,82 +41,122 @@ import ( "go.step.sm/crypto/pemutil" ) +var baseURLFlag string +var ignoreExistingSignatureFlag bool + // signCmd represents the sign command var signCmd = &cobra.Command{ Use: "sign", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Args: cobra.MinimumNArgs(1), + Short: "Signs a MUD file", + Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - filepath := args[0] - json, err := internal.Contents(filepath) + data, err := internal.Read(filepath) if err != nil { - return errors.Wrapf(err, "retrieving contents from %s failed", filepath) + return errors.Wrapf(err, "error reading contents of %s", filepath) } - - mudfile := &mudyang.Mudfile{} - if err := mudyang.Unmarshal(json, mudfile); err != nil { - return errors.Wrapf(err, "unmarshaling JSON failed") + mudfile, err := internal.Parse(data) + if err != nil { + return errors.Wrap(err, "could not get contents") } - // TODO: provide flag to override an existing signature - // if mudHasSignature(mud) { - // fmt.Printf("this MUD already has a signature available at: %s\n", *mud.Mud.MudSignature) - // return + // TODO: provide option to first emit standard MUD JSON and use the JSON + // representation of that as the material to be signed? + + // TODO: look into this logic: if a signature path is know, the signature may already + // exist or not yet. If it does already exist, we may want to re-sign the MUD file. + // The location of the new signature can be assumed to be the same in the end. + // if !ignoreExistingSignatureFlag && mudHasSignature(mudfile) { + // return fmt.Errorf("this MUD already has a signature available at: %s", *mudfile.Mud.MudSignature) // } - signaturePath, err := internal.SignaturePath(filepath) - fmt.Println("signature path: ", signaturePath) + existingMudUrl, err := internal.MUDURL(mudfile) + fmt.Println("existing mud url: ", existingMudUrl) + if err != nil { + return errors.Wrap(err, "retrieving MUD URL from MUD failed") + } + + existingMudSignatureUrl, err := internal.MUDSignatureURL(mudfile) + fmt.Println("existing mud signature: ", existingMudSignatureUrl) + if err != nil { + return errors.Wrap(err, "retrieving MUD signature URL from MUD failed") + } + + signatureFilename, err := internal.SignatureFilename(filepath) + fmt.Println("signature path: ", signatureFilename) if err != nil { return errors.Wrap(err, "retrieving signature path from MUD failed") } + newMudURL := existingMudUrl + fmt.Println("new MUD url: ", newMudURL) + newSignatureURL := internal.NewMUDSignatureURL(existingMudUrl, signatureFilename) + fmt.Println("new signature url: ", newSignatureURL) + + if baseURLFlag != "" { + newMudURL, err = rewriteBase(newMudURL, baseURLFlag) + if err != nil { + return errors.Wrap(err, "rewriting base URL for MUD URL failed") + } + newSignatureURL, err = rewriteBase(newSignatureURL, baseURLFlag) + if err != nil { + return errors.Wrap(err, "rewriting base URL for MUD signature URL failed") + } + } + + fmt.Println("new MUD url: ", newMudURL) + fmt.Println("new signature url: ", newSignatureURL) + + //var signatureURL *url.URL + // if baseURLFlag != "" { + // baseURL, err := url.Parse(baseURLFlag) + // if err != nil { + // return errors.Wrap(err, "failed parsing base URL") + // } + // signatureURL = baseURL + // signatureURL.Path = signaturePath // TODO: support path with multiple segments + // } else { + // signatureURL = mudURL + // signatureURL.Path = signaturePath // TODO: support path with multiple segments + // } + // TODO: update Mudfile with location for signature? Needs to be clear that it has indeed be changed. // TODO: if signing a local file, provide argument for the full path or directory for the signature file, so // that the right value can be added to the MUD file before signing. - output, err := ygot.DeepCopy(mudfile) + copy, err := ygot.DeepCopy(mudfile) if err != nil { return errors.Wrap(err, "creating deep copy of MUD YANG representation failed") } - outputMudfile, ok := output.(*mudyang.Mudfile) + copyMUDFile, ok := copy.(*mudyang.Mudfile) if !ok { return errors.New("the output MUD YANG is not a *mudyang.Mudfile") } - // TODO: if changing the signature, should we update the updated_at too? And/or others? - outputMudfile.Mud.MudSignature = &signaturePath + // TODO: change other properties? + mudURLString := newMudURL.String() + copyMUDFile.Mud.MudUrl = &mudURLString + signatureURLString := newSignatureURL.String() + copyMUDFile.Mud.MudSignature = &signatureURLString - n, err := ygot.Diff(mudfile, outputMudfile) + diff, err := ygot.Diff(mudfile, copyMUDFile) if err != nil { return errors.Wrap(err, "diffing the input and output MUD file failed") } - // TODO: after diffing, check only expected number of changes were made - log.Println("diff: ", n) - - //jsonString, err := ygot.EmitJSON(outputMudfile, &ygot.EmitJSONConfig{ - jsonString, err := ygot.EmitJSON(mudfile, &ygot.EmitJSONConfig{ - Format: ygot.RFC7951, - Indent: " ", - RFC7951Config: &ygot.RFC7951JSONConfig{ - AppendModuleName: true, - }, - SkipValidation: false, - // TODO: other validation options? - }) - if err != nil { - return errors.Wrap(err, "could not marshal MUD file into JSON") - } + // TODO: can the diff be printed nicely (easily)? It seems to be some text values ... + log.Println("diff: ", diff) - data := []byte(jsonString) + differencesFound := len(diff.GetDelete())+len(diff.GetUpdate()) > 0 + if differencesFound { + // TODO: in case we're using the copy, we probably also need to update the updated_at + json, err := internal.JSON(copyMUDFile) + if err != nil { + return errors.Wrap(err, "getting JSON representation of MUD file failed") + } + data = []byte(json) + } fmt.Println(data) @@ -180,6 +222,9 @@ to quickly create a Cobra application.`, return errors.Wrap(err, "writing DER signature failed") } + // TODO: if differences were found, we also should store the new + // MUD file, so that it can be uploaded later. + fmt.Println(signature) // TODO: print output on how/where to store the file + signature? @@ -237,16 +282,33 @@ func mudHasSignature(mud *mudyang.Mudfile) bool { return mud.Mud.MudSignature != nil } +func rewriteBase(u *url.URL, baseURLString string) (*url.URL, error) { + // TODO: check this works as expected + base, err := url.Parse(baseURLString) + if err != nil { + return nil, err + } + path := u.EscapedPath() + filename := fp.Base(path) + baseString := base.String() + if !strings.HasSuffix(baseString, "/") { + baseString = baseString + "/" + } + newURL, err := url.Parse(baseString + filename) + if err != nil { + return nil, err + } + newURL.RawQuery = u.RawQuery + newURL.Fragment = u.Fragment + return newURL, nil +} + func init() { rootCmd.AddCommand(signCmd) - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // signCmd.PersistentFlags().String("foo", "", "A help for foo") + // TODO: provide a flag that uses a base URL for the MUD URL and signature to exist in the file? - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // signCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + signCmd.PersistentFlags().StringVarP(&baseURLFlag, "base-url", "u", "", "Base URL to use for MUD URL and signature location") + signCmd.PersistentFlags().StringVarP(&signatureFlag, "signature", "s", "", "Location of signature file to set") + signCmd.PersistentFlags().BoolVar(&ignoreExistingSignatureFlag, "ignore-existing-signature", false, "Ignore") } diff --git a/cmd/validate.go b/cmd/validate.go index a6ebe33..b6f5530 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -19,8 +19,6 @@ import ( "fmt" "github.com/hslatman/mud-cli/internal" - "github.com/hslatman/mud.yang.go/pkg/mudyang" - "github.com/openconfig/ygot/ytypes" "github.com/pkg/errors" "github.com/spf13/cobra" ) @@ -28,39 +26,22 @@ import ( // validateCmd represents the validate command var validateCmd = &cobra.Command{ Use: "validate", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Args: cobra.MinimumNArgs(1), + Short: "Validates a MUD file to be formatted correctly", + Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { filepath := args[0] - json, err := internal.Contents(filepath) + mudfile, err := internal.ReadMUDFileFrom(filepath) if err != nil { - return errors.Wrap(err, "error reading file contents") + return errors.Wrapf(err, "could not get contents from %s", filepath) } - mud := &mudyang.Mudfile{} - if err := mudyang.Unmarshal(json, mud); err != nil { - return errors.Wrap(err, "can't unmarshal JSON") - } - // TODO: more validation options? - options := &ytypes.LeafrefOptions{ - IgnoreMissingData: false, - Log: true, - } - if err = mud.Validate(options); err != nil { - println(fmt.Sprintf("Error validating MUD: %v", err)) - return errors.Wrap(err, "Error validating MUD") + err = internal.Validate(mudfile) + if err != nil { + return errors.Wrap(err, "error validating MUD file") } - // TODO: some way to get more errors at once, if possible? - // Or some nicer output. - + // TODO: some way to get more errors at once, if possible? Or some nicer output. fmt.Println("MUD is valid") return nil @@ -69,14 +50,4 @@ to quickly create a Cobra application.`, func init() { rootCmd.AddCommand(validateCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // validateCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // validateCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } diff --git a/cmd/verify.go b/cmd/verify.go index 330bb42..9b6350e 100644 --- a/cmd/verify.go +++ b/cmd/verify.go @@ -25,56 +25,56 @@ import ( cms "github.com/github/ietf-cms" "github.com/hslatman/mud-cli/internal" "github.com/hslatman/mud.yang.go/pkg/mudyang" - "github.com/openconfig/ygot/ygot" "github.com/pkg/errors" "github.com/spf13/cobra" "go.step.sm/crypto/pemutil" ) +var signatureFlag string + // verifyCmd represents the verify command var verifyCmd = &cobra.Command{ Use: "verify", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, + Short: "Verifies the signature for a MUD file", RunE: func(cmd *cobra.Command, args []string) error { filepath := args[0] - json, err := internal.Contents(filepath) + data, err := internal.Read(filepath) if err != nil { - return errors.Wrap(err, "error reading file contents") + errors.Wrapf(err, "error reading contents of %s", filepath) } - - mud := &mudyang.Mudfile{} - if err := mudyang.Unmarshal(json, mud); err != nil { - return errors.Wrap(err, "can't unmarshal JSON") + mudfile, err := internal.Parse(data) + if err != nil { + return errors.Wrap(err, "could not get contents") } - - signaturePath, err := internal.SignaturePath(filepath) - fmt.Println("signature path: ", signaturePath) + signature, err := getSignatureFilepath(mudfile) if err != nil { - return errors.Wrap(err, "retrieving signature path from MUD failed") + return errors.Wrap(err, "retrieving signature from MUD failed") } + fmt.Println("signature path: ", signature) + + // signaturePath, err := internal.SignaturePath(filepath) + // fmt.Println("signature path: ", signaturePath) + // if err != nil { + // return errors.Wrap(err, "retrieving signature path from MUD failed") + // } + // TODO: read MUD signature file location from MUD file, retrieve it and verify using that one // TODO: allow for providing the signature file as an argument - jsonString, err := ygot.EmitJSON(mud, &ygot.EmitJSONConfig{ - Format: ygot.RFC7951, - Indent: " ", - RFC7951Config: &ygot.RFC7951JSONConfig{ - AppendModuleName: true, - }, - SkipValidation: false, - }) - if err != nil { - return errors.Wrap(err, "could not marshal MUD file into JSON") - } + // jsonString, err := ygot.EmitJSON(mudfile, &ygot.EmitJSONConfig{ + // Format: ygot.RFC7951, + // Indent: " ", + // RFC7951Config: &ygot.RFC7951JSONConfig{ + // AppendModuleName: true, + // }, + // SkipValidation: false, + // }) + // if err != nil { + // return errors.Wrap(err, "could not marshal MUD file into JSON") + // } - data := []byte(jsonString) + // data := []byte(jsonString) fmt.Println(data) @@ -115,16 +115,22 @@ to quickly create a Cobra application.`, }, } +func getSignatureFilepath(mudfile *mudyang.Mudfile) (string, error) { + if signatureFlag != "" { + return signatureFlag, nil + } + if mudfile.Mud == nil { + return "", errors.New("no 'mud' property found in MUD file") + } + sig := mudfile.Mud.MudSignature + if sig == nil { + return "", errors.New("no signature found in MUD file") + } + return *sig, nil +} + func init() { rootCmd.AddCommand(verifyCmd) - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // verifyCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // verifyCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + verifyCmd.PersistentFlags().StringVarP(&signatureFlag, "signature", "s", "", "Location of signature file to use") } diff --git a/cmd/view.go b/cmd/view.go index c76cb47..ce5968b 100644 --- a/cmd/view.go +++ b/cmd/view.go @@ -24,33 +24,15 @@ import ( // viewCmd represents the view command var viewCmd = &cobra.Command{ Use: "view", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, + Short: "Provides a graphical view of a MUD file", Run: func(cmd *cobra.Command, args []string) { fmt.Println("view called") - - // TODO: check if file exists or download temp copy + // TODO: check if file exists locally or download temp copy // TODO: serve the file locally; temporarily // TODO: open browser; show MUD Visualizer with the chosen MUD file - // }, } func init() { rootCmd.AddCommand(viewCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // viewCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // viewCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") } diff --git a/go.mod b/go.mod index 85100a9..3d9ea34 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/github/ietf-cms v0.1.0 github.com/hslatman/mud.yang.go v0.0.0-20201016091044-991f3fbb23d0 github.com/openconfig/ygot v0.11.2 + github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.2.1 go.step.sm/crypto v0.9.0 ) diff --git a/internal/json.go b/internal/json.go new file mode 100644 index 0000000..928bc25 --- /dev/null +++ b/internal/json.go @@ -0,0 +1,26 @@ +package internal + +import ( + "github.com/hslatman/mud.yang.go/pkg/mudyang" + "github.com/openconfig/ygot/ygot" + "github.com/pkg/errors" +) + +func JSON(mudfile *mudyang.Mudfile) (string, error) { + json, err := ygot.EmitJSON(mudfile, &ygot.EmitJSONConfig{ + Format: ygot.RFC7951, + Indent: " ", + RFC7951Config: &ygot.RFC7951JSONConfig{ + AppendModuleName: true, + }, + SkipValidation: false, // TODO: provide flag to skip? + }) + if err != nil { + return "", errors.Wrap(err, "could not marshal MUD file into JSON") + } + // TODO: ygot will alphabetically order properties (mapJSON); do we want some + // way to override this, so that the informational parts can be shown in the top? + // Of course this impacts ordering of the output, so care should be taken that + // this behavior is known to the user. + return json, nil +} diff --git a/internal/signature.go b/internal/mud.go similarity index 57% rename from internal/signature.go rename to internal/mud.go index bdb4053..26080b2 100644 --- a/internal/signature.go +++ b/internal/mud.go @@ -3,9 +3,11 @@ package internal import ( "net/url" fp "path/filepath" + + "github.com/hslatman/mud.yang.go/pkg/mudyang" ) -func SignaturePath(filepath string) (string, error) { +func SignatureFilename(filepath string) (string, error) { var filename string if isURL(filepath) { u, err := url.Parse(filepath) @@ -13,8 +15,9 @@ func SignaturePath(filepath string) (string, error) { return "", err } filename = fp.Base(u.EscapedPath()) - u.Path = filename + ".p7s" - return u.String(), nil + return filename + ".p7s", nil + //u.Path = filename + ".p7s" + //return u.String(), nil } // TODO: we probably need to change this for local files. @@ -22,7 +25,29 @@ func SignaturePath(filepath string) (string, error) { // be put online, so that that can be used as the signature // path in the MUD file and then can be signed. filename = fp.Base(filepath) + ".p7s" - return fp.Join(fp.Dir(filepath), filename), nil + //return fp.Join(fp.Dir(filepath), filename), nil + return filename, nil +} + +func MUDURL(mudfile *mudyang.Mudfile) (*url.URL, error) { + return url.Parse(*mudfile.Mud.MudUrl) +} + +func MUDSignatureURL(mudfile *mudyang.Mudfile) (*url.URL, error) { + sig := mudfile.Mud.MudSignature + if sig == nil { + return nil, nil + } + return url.Parse(*sig) +} + +func NewMUDSignatureURL(mudurl *url.URL, filename string) *url.URL { + sigURL := *mudurl + path := sigURL.EscapedPath() + mudFileName := fp.Base(path) + path = path[0:len(path)-len(mudFileName)] + filename + sigURL.Path = path + return &sigURL } // func SignaturePath(filepath string) (string, error) { diff --git a/internal/read.go b/internal/read.go index 37edb1c..be59004 100644 --- a/internal/read.go +++ b/internal/read.go @@ -7,10 +7,15 @@ import ( "net/http" "net/url" "os" + + "github.com/hslatman/mud.yang.go/pkg/mudyang" + "github.com/pkg/errors" ) -func Contents(filepath string) ([]byte, error) { +func Read(filepath string) ([]byte, error) { if isURL(filepath) { + // TODO: provide additional parameters for filetype to retrieve (or use a MUD client abstraction?) + // so that the right headers can be provided in the requests (and check these in the response?) // TODO: RFC states that MUD URL should be HTTPS; fail on that here if not? f, err := ioutil.TempFile(os.TempDir(), "") if err != nil { @@ -22,7 +27,9 @@ func Contents(filepath string) ([]byte, error) { return []byte{}, err } defer resp.Body.Close() - + if resp.StatusCode >= 400 { + return []byte{}, fmt.Errorf("retrieving MUD file from %s resulted in HTTP status %d", filepath, resp.StatusCode) + } _, err = io.Copy(f, resp.Body) if err != nil { return []byte{}, err @@ -38,9 +45,24 @@ func Contents(filepath string) ([]byte, error) { return json, nil } +func Parse(data []byte) (*mudyang.Mudfile, error) { + mud := &mudyang.Mudfile{} + // TODO: provide options for unmarshaling? + if err := mudyang.Unmarshal(data, mud); err != nil { + return nil, errors.Wrap(err, "can't unmarshal JSON") + } + return mud, nil +} + +func ReadMUDFileFrom(filepath string) (*mudyang.Mudfile, error) { + json, err := Read(filepath) + if err != nil { + return nil, errors.Wrap(err, "error reading file contents") + } + return Parse(json) +} + func isURL(str string) bool { u, err := url.Parse(str) - fmt.Println(err) - fmt.Println(u) return err == nil && u.Scheme != "" && u.Host != "" } diff --git a/internal/validate.go b/internal/validate.go new file mode 100644 index 0000000..66fe501 --- /dev/null +++ b/internal/validate.go @@ -0,0 +1,18 @@ +package internal + +import ( + "github.com/hslatman/mud.yang.go/pkg/mudyang" + "github.com/openconfig/ygot/ytypes" +) + +func Validate(mudfile *mudyang.Mudfile) error { + // TODO: more validation options? + options := &ytypes.LeafrefOptions{ + IgnoreMissingData: false, + Log: true, + } + if err := mudfile.Validate(options); err != nil { + return err + } + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 541be31..1d88398 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -41,6 +41,7 @@ github.com/openconfig/ygot/util github.com/openconfig/ygot/ygot github.com/openconfig/ygot/ytypes # github.com/pkg/errors v0.9.1 +## explicit github.com/pkg/errors # github.com/spf13/cobra v1.2.1 ## explicit