/* Store a private key in PEM format into a file in the standard directory in the relish installation, using standard file naming convention. */ func StorePrivateKey(entityType string, entityName string, privateKeyPEM string) (err error) { fileName := entityType + "__" + entityName + "__private_key.pem" path := relishRuntimeLocation + "/keys/private/" + fileName var perm os.FileMode = 0666 err = gos.WriteFile(path, ([]byte)(privateKeyPEM), perm) return }
/* Store a public key certificate in PEM format into a file in the standard directory in the relish installation, using standard file naming convention. */ func StorePublicKeyCert(entityType string, entityName string, publicKeyCertPEM string) (err error) { fileName := entityType + "__" + entityName + "__public_key.pem" path := relishRuntimeLocation + "/keys/public/" + fileName var perm os.FileMode = 0666 err = gos.WriteFile(path, ([]byte)(publicKeyCertPEM), perm) return }
/* Store a public key certificate in PEM format into a file in the standard directory in the relish installation, using standard file naming convention. */ func StorePublicKeyCert(entityType string, entityName string, publicKeyCertPEM string) (err error) { fileName := entityType + "__" + entityName + "__public_key.pem" keyDirPath := relishRuntimeLocation + "/keys/public" path := keyDirPath + "/" + fileName var perm os.FileMode = 0777 err = gos.MkdirAll(keyDirPath, perm) if err != nil { return } perm = 0666 err = gos.WriteFile(path, ([]byte)(publicKeyCertPEM), perm) return }
func copyDocDirTree(fromDocDirPath string, toDocDirPath string) (err error) { var dir *os.File var filesInDir []os.FileInfo dir, err = gos.Open(fromDocDirPath) filesInDir, err = dir.Readdir(0) if err != nil { return } err = dir.Close() err = gos.Mkdir(toDocDirPath, 0777) for _, fileInfo := range filesInDir { fromItemPath := fromDocDirPath + "/" + fileInfo.Name() toItemPath := toDocDirPath + "/" + fileInfo.Name() if fileInfo.IsDir() { err = copyDocDirTree(fromItemPath, toItemPath) if err != nil { return } } else { // plain old file to be copied. if strings.HasSuffix(fileInfo.Name(), ".txt") || strings.HasSuffix(fileInfo.Name(), ".html") || strings.HasSuffix(fileInfo.Name(), ".htm") || strings.HasSuffix(fileInfo.Name(), ".css") { var content []byte content, err = gos.ReadFile(fromItemPath) if err != nil { return } err = gos.WriteFile(toItemPath, content, 0666) if err != nil { return } } } } return }
/* copy file1 String file2 String > err String copy file1 String file2 permissions > err String */ func copy(th InterpreterThread, objects []RObject) []RObject { filePath1 := string(objects[0].(String)) filePath2 := string(objects[1].(String)) permStr := "766" if len(objects) > 2 { permStr = string(objects[2].(String)) } perm, errStr := getFilePermissions(permStr) if errStr == "" { content, err := gos.ReadFile(filePath1) if err != nil { errStr = err.Error() } else { err = gos.WriteFile(filePath2, content, perm) if err != nil { errStr = err.Error() } } } return []RObject{String(errStr)} }
/* Creates a directory tree for a new relish artifact. Creates template versions of some of the files you will need. projectType can currently be "webapp" or "" */ func initProject(relishRoot, projectPath, projectType string) (err error) { artifactDir := relishRoot + "/artifacts/" + projectPath slashPos := strings.Index(projectPath, "/") origin := projectPath[0:slashPos] artifact := projectPath[slashPos+1:] metadataFilePath := artifactDir + "/metadata.txt" date := time.Now().Format("2006/01/02") err = gos.MkdirAll(artifactDir, 0777) if err != nil { return } mainPackageDir := artifactDir + "/v0.1.0/src/main" err = gos.MkdirAll(mainPackageDir, 0777) if err != nil { return } mainFilePath := mainPackageDir + "/main.rel" if projectType == "webapp" { metadata := fmt.Sprintf(WEB_APP_METADATA_FILE, date, origin, artifact) err = gos.WriteFile(metadataFilePath, ([]byte)(metadata), 0777) if err != nil { return } // TODO Create web package directory with template index.html and dialog.rel and static/ with styles and an image and a static html file. webPackageDir := artifactDir + "/v0.1.0/src/web" err = gos.MkdirAll(webPackageDir, 0777) if err != nil { return } indexPath := webPackageDir + "/index.html" err = gos.WriteFile(indexPath, ([]byte)(INDEX_HTML_FILE), 0777) if err != nil { return } visitCountPath := webPackageDir + "/visit_count.html" err = gos.WriteFile(visitCountPath, ([]byte)(VISIT_COUNT_HTML_FILE), 0777) if err != nil { return } gotItPath := webPackageDir + "/got_it.html" err = gos.WriteFile(gotItPath, ([]byte)(GOT_IT_HTML_FILE), 0777) if err != nil { return } tryAgainPath := webPackageDir + "/try_again.html" err = gos.WriteFile(tryAgainPath, ([]byte)(TRY_AGAIN_HTML_FILE), 0777) if err != nil { return } dialogPath := webPackageDir + "/dialog.rel" dialogContent := fmt.Sprintf(DIALOG_FILE, origin, artifact) err = gos.WriteFile(dialogPath, ([]byte)(dialogContent), 0777) if err != nil { return } stylesDir := webPackageDir + "/static/styles" err = gos.MkdirAll(stylesDir, 0777) if err != nil { return } cssPath := stylesDir + "/default.css" err = gos.WriteFile(cssPath, ([]byte)(CSS_FILE), 0777) if err != nil { return } staticPath := webPackageDir + "/static/example.html" err = gos.WriteFile(staticPath, ([]byte)(STATIC_HTML_FILE), 0777) if err != nil { return } imgDir := webPackageDir + "/static/img" err = gos.MkdirAll(imgDir, 0777) if err != nil { return } imagePath := imgDir + "/relish_logo_small.png" var imageBytes []byte imageBytes, err = base64.URLEncoding.DecodeString(RELISH_LOGO_PNG) if err != nil { return } err = gos.WriteFile(imagePath, imageBytes, 0777) if err != nil { return } mainContent := fmt.Sprintf(WEB_APP_MAIN_PROGRAM_FILE, origin, artifact, artifact, origin, artifact) err = gos.WriteFile(mainFilePath, ([]byte)(mainContent), 0777) if err != nil { return } // TODO create index.html, dialog.rel,default.css,an image??,a static html file. } else if projectType == "" { metadata := fmt.Sprintf(APP_METADATA_FILE, date, origin, artifact) err = gos.WriteFile(metadataFilePath, ([]byte)(metadata), 0777) if err != nil { return } mainContent := fmt.Sprintf(APP_MAIN_PROGRAM_FILE, origin, artifact, artifact, origin, artifact) err = gos.WriteFile(mainFilePath, ([]byte)(mainContent), 0777) if err != nil { return } } else { err = fmt.Errorf("Unrecognized relish project type '%s'.", projectType) } return }
/* Fetches and, if newer, installs metadata.txt file in shared/relish/replicas directory. */ func fetchArtifactMetadata(hostUrl string, originAndArtifactPath string, localMetadataDate string, existingSharedCurrentVersion string, sharedArtifactMetadataFilePath string) (currentVersion string, err error) { slashPos := strings.Index(originAndArtifactPath, "/") originDomain := originAndArtifactPath[0 : slashPos-4] hostDomain := hostUrl[7:] var dir string if originDomain == hostDomain || strings.HasPrefix(hostDomain, "localhost") { dir = "artifacts" } else { dir = "replicas" } url := hostUrl + "/relish/" + dir + "/" + originAndArtifactPath + "/metadata.txt" var resp *http.Response var body []byte //resp, err = http.Get(url) resp, err = artifactLoaderClient.Get(url) if err != nil { return } defer resp.Body.Close() if resp.StatusCode == 404 { // HTTP Page/Resource Not Found err = fmt.Errorf("%s not found at servers tried.", originAndArtifactPath) return } else if resp.StatusCode >= 400 { // Other HTTP error err = fmt.Errorf("%s not found or HTTP errors at servers tried.", originAndArtifactPath) return } body, err = ioutil.ReadAll(resp.Body) if err != nil { return } var remoteCurrentVersion string var remoteMetadataDate string remoteCurrentVersion, remoteMetadataDate, err = readCurrentVersion(body, url) if err != nil { return } if remoteMetadataDate > localMetadataDate { currentVersion = remoteCurrentVersion // Create local directory for replica artifact, if necessary var perm os.FileMode = 0777 artifactReplicaDir := sharedArtifactMetadataFilePath[:len(sharedArtifactMetadataFilePath)-len("/metadata.txt")] err = gos.MkdirAll(artifactReplicaDir, perm) if err != nil { return } // Write body to local shared metadata.txt file err = gos.WriteFile(sharedArtifactMetadataFilePath, body, perm) if err != nil { return } } else { currentVersion = existingSharedCurrentVersion } return }
/* Loads the code from the specified package into the runtime. Recurses to load packages which the argument package depends on. TODO artifact version preferences are NOT being handled correctly yet. What should happen is that all artifacts whose versions "ARE" recursively preferred should have their version preferences consulted and respected first (closer to top of load-tree has preference priority) and then and only then should a single no-version-preferred artifact be loaded by asking what its latest version is, then re-visit if that constrains other formerly no-version-preferred ones, then load newly preferred versions, then load the next no-version-preferred artifact. Currently, an artifact version may be loaded in a no-version-preferred way, because the current load-tree descent path does not prefer a version, but it could be that a subsequently loaded artifact somewhere else in the load-tree COULD express a preference for a different version of the artifact, but its preference is consulted too late, after the artifact is already loaded. Handles searching first in a local (private) artifacts repository (directory tree) then a shared artifacts repository, but only if a directive is not in effect to load from shared only, and only if another package from the same artifact has not already been loaded, because in that case, the local (private) or shared decision has already been made and must apply to subsequent packages loaded from the same artifact. If not found locally, tries to load from the Internet (at several standard locations). TODO signed-code integrity checks */ func (ldr *Loader) LoadPackage(originAndArtifactPath string, version string, packagePath string, mustBeFromShared bool) (gen *generator.Generator, err error) { if Logging(PARSE_) { parserDebugMode |= parser.Trace } // First, see if the package is already loaded. If so, return packageIdentifier := originAndArtifactPath + "/pkg/" + packagePath beingLoaded := ldr.PackagesBeingLoaded[packageIdentifier] if beingLoaded { err = fmt.Errorf("Package dependency loop. Package '%s' is also present in the tree of packages it imports.", packageIdentifier) return } loadedVersion, found := ldr.LoadedPackages[packageIdentifier] if found { if loadedVersion != version && version != "" { err = fmt.Errorf("Can't load version %s of '%s' since version %s is already loaded into runtime.", version, packageIdentifier, loadedVersion) } return } localArtifactMetadataFilePath := ldr.RelishRuntimeLocation + "/artifacts/" + originAndArtifactPath + "/metadata.txt" sharedArtifactMetadataFilePath := ldr.RelishRuntimeLocation + "/shared/relish/artifacts/" + originAndArtifactPath + "/metadata.txt" sharedReplicaMetadataFilePath := ldr.RelishRuntimeLocation + "/shared/relish/replicas/" + originAndArtifactPath + "/metadata.txt" // Current version of artifact according to shared artifact metadata found in this relish directory tree. sharedCurrentVersion := "" // Date of the artifact metadata that is being relied on for the package load. metadataDate := "" ldr.PackagesBeingLoaded[packageIdentifier] = true if !ldr.quiet { Log(ALWAYS_, "Loading package %s\n", packageIdentifier) } Log(LOAD2_, "LoadPackage: ldr.RelishRuntimeLocation=%s\n", ldr.RelishRuntimeLocation) Log(LOAD2_, "LoadPackage: originAndArtifactPath=%s\n", originAndArtifactPath) Log(LOAD2_, "LoadPackage: version=%s\n", version) Log(LOAD2_, "LoadPackage: packagePath=%s\n", packagePath) mustBeFromShared = mustBeFromShared || ldr.SharedCodeOnly // Set whether will consider local code for this package. var mustBeFromLocal bool // We may end up constrained to load from local artifact. // Package is not loaded. But see if any other packages from the same artifact are loaded. // If so, make sure they don't have an incompatible version. var artifactAlreadyLoaded bool // if true, at least one package from the currently-being-loaded artifact has already been loade. // This means the needed version of the artifact and the artifacts it depends on have already // been loaded from built.txt into LoadedArtifacts map. var artifactKnownToBeLocal bool // if artifact is loaded or being loaded, is it loaded from local var artifactKnownToBePublished bool // if artifact is loaded or being loaded, is it loaded from shared var artifactKnownToBeReplica bool // if artifact is loaded or being loaded, is it downloaded and loaded from shared/replicas loadedVersion, artifactAlreadyLoaded = ldr.LoadedArtifacts[originAndArtifactPath] if artifactAlreadyLoaded { if loadedVersion != version && version != "" { err = fmt.Errorf("Can't load package '%s' from version %s of '%s'. Another package from version %s of the artifact is already loaded into runtime.", packagePath, version, originAndArtifactPath, loadedVersion) return } artifactKnownToBeLocal = ldr.LoadedArtifactKnownToBeLocal[originAndArtifactPath] artifactKnownToBePublished = ldr.LoadedArtifactKnownToBePublished[originAndArtifactPath] artifactKnownToBeReplica = ldr.LoadedArtifactKnownToBeReplica[originAndArtifactPath] mustBeFromShared = mustBeFromShared || artifactKnownToBePublished || artifactKnownToBeReplica // Set whether will consider local code for this package. Log(LOAD2_, "%s %s mustBeFromShared=%v\n", originAndArtifactPath, packagePath, mustBeFromShared) if artifactKnownToBeLocal { if mustBeFromShared { // This should never happen I think. Check anyway. err = fmt.Errorf("Can't load package '%s' from shared artifact '%s'. Another package from the local copy of the artifact is already loaded into runtime.", packagePath, originAndArtifactPath) return } else { mustBeFromLocal = true } } } // Now try to load the package from local file system. // If allowed, need to try twice, trying to read from // local artifacts dir tree, then if not found from shared artifacts dir tree. //// If no version has been specified, but some version of the artifact exists in local disk, //// the first thing to do is to read the metadata.txt of the local artifact, and set the version number desired //// to the current version as specified by the local artifact copy. Note this could be out of date, but we need a //// different command to go check if there is a later version of the artifact out there and download it. //// If we can find a metadata.txt file for the artifact locally, set the version with it. if version == "" { if !mustBeFromShared { version, metadataDate, err = ldr.readMetadataFile(localArtifactMetadataFilePath) if err != nil { return } } if version == "" { if mustBeFromLocal { // We already loaded a package from the local artifact, but somehow the local artifact is no longer there on filesystem. // This should never happen if everything is being loaded at once at beginning of run. Check anyway. err = fmt.Errorf("Can't load package '%s' from local artifact '%s'. Local artifact not found.", packagePath, originAndArtifactPath) return } version, metadataDate, err = ldr.readMetadataFile(sharedArtifactMetadataFilePath) if err != nil { return } if version == "" { version, metadataDate, err = ldr.readMetadataFile(sharedReplicaMetadataFilePath) if err != nil { return } if version != "" { artifactKnownToBeReplica = true } } else { artifactKnownToBePublished = true } sharedCurrentVersion = version } } // stat the artifact version dir to see if the version of the artifact exists in the filesystem. // // try local then shared artifacts and replicas dir trees as allowed by constraints so far artifactVersionDirFound := false var artifactVersionDir string if version != "" { versionStr := "/v" + version if !mustBeFromShared { // try local artifacts dir tree artifactVersionDir = ldr.RelishRuntimeLocation + "/artifacts/" + originAndArtifactPath + versionStr _, statErr := gos.Stat(artifactVersionDir) if statErr != nil { if !os.IsNotExist(statErr) { err = fmt.Errorf("Can't stat relish artifact version directory '%s': %v\n", artifactVersionDir, statErr) return } } else { artifactVersionDirFound = true mustBeFromLocal = true // locked to local (private) artifact now artifactKnownToBeLocal = true } } if !artifactVersionDirFound { // this version not found in local artifacts dir tree // try published shared artifacts dir tree artifactVersionDir = ldr.RelishRuntimeLocation + "/shared/relish/artifacts/" + originAndArtifactPath + versionStr _, statErr := gos.Stat(artifactVersionDir) if statErr != nil { if !os.IsNotExist(statErr) { err = fmt.Errorf("Can't stat relish artifact version directory '%s': %v\n", artifactVersionDir, statErr) return } } else { artifactVersionDirFound = true mustBeFromShared = true // locked to shared artifact now artifactKnownToBePublished = true } } if !artifactVersionDirFound { // this version not found in local artifacts dir tree or published shared artifacts dir tree // try downloaded shared replicas dir tree artifactVersionDir = ldr.RelishRuntimeLocation + "/shared/relish/replicas/" + originAndArtifactPath + versionStr _, statErr := gos.Stat(artifactVersionDir) if statErr != nil { if !os.IsNotExist(statErr) { err = fmt.Errorf("Can't stat relish artifact version directory '%s': %v\n", artifactVersionDir, statErr) return } } else { artifactVersionDirFound = true mustBeFromShared = true // locked to shared artifact now artifactKnownToBeReplica = true } } } if !artifactVersionDirFound { // Have not found the artifact version locally. Fetch it from the Internet. Log(LOAD2_, "artifact version |%s| not found locally\n", version) Log(LOAD2_, "localArtifactMetadataFilePath=%s\n", localArtifactMetadataFilePath) // TODO Need this path in order to install or update the artifact metadata file from remote, if there is none locally // or if the remote one is more recent. // // artifactMetadataFilePath := ldr.RelishRuntimeLocation + "/shared/relish/artifacts/" + originAndArtifactPath + "/metadata.txt" // Note: We will always be fetching into the shared artifacts directory tree. // If programmer wants to copy an artifact version into the local artifacts directory tree to develop/modify it, // they must currently do that copy separately manually. var zipFileContents []byte if sharedCurrentVersion == "" { sharedCurrentVersion, metadataDate, err = ldr.readMetadataFile(sharedReplicaMetadataFilePath) } // replaced by stuff below // defaultHostURL := ldr.DefaultCodeHost(originAndArtifactPath) // hostURL := defaultHostURL // // REVIEW THIS COMMENT // Note: TODO The correct order to do things is to load the metadata.txt file from the default host // (if possible) then to search for secondary hosts to get the version zip file, selecting // one AT RANDOM, // then if all of those (some number of) mirrors fail, get it from the default host // Also, use port 80 then 8421. // TODO: RE: SERVER SEARCH ORDER // We really ought to consider using a different order of servers tried // for fetching artifact source-code zip files than the order we use for trying to find // the smaller artifact metadata.txt files. Specifically, it is better to find // metadata.txt files at servers owned or controlled by the origin, because the metadata.txt // file will be up to date. shared.relish.pl is next best for that consideration. // However, from a performance (load sharing when scaled) perspective, it is better to // download the actual source code zip files from randomly found replica servers or // secondary general repositories. hostURLs := ldr.NonSearchedCodeHostURLs(originAndArtifactPath, "") // NEW // If we did not have the metadata file on filesystem before, or if remote metadata file is newer, // we should download and cache the metadata.txt file from the remote repository. // // Then, if we do not have a specified version yet, we should set version # from that, var currentVersion string var usingCentralRepo bool = false // whether getting code from http://shared.relish.pl var hostURL string for _, hostURL = range hostURLs { // Read remote metadata file. Store it locally if its date is >= the local shared artifact metadata date. currentVersion, err = fetchArtifactMetadata(hostURL, originAndArtifactPath, metadataDate, sharedCurrentVersion, sharedReplicaMetadataFilePath) if err == nil { break } else if currentVersion != "" { return // Inability to create or write local metadata file. Bad error. } } if currentVersion == "" { // TODO Now try google search // Should really do a Google search for metadata found anywhere (except shared.relish.pl) now, // so as to limit load and single point of failure on shared.relish.pl. /* hostURLs,err = ldr.FindSecondaryCodeHosts(originAndArtifactPath, hostURLs) if err != nil { return } for _,hostURL = range hostURLs { // Read remote metadata file. Store it locally if its date is >= the local shared artifact metadata date. currentVersion, err = fetchArtifactMetadata(hostURL, originAndArtifactPath, metadataDate, sharedCurrentVersion, sharedReplicaMetadataFilePath) if err == nil { break } else if currentVersion != "" { return // Inability to create or write local metadata file. Bad error. } } */ // // Only if trying other replica sites fails should shared.relish.pl be tried. // // However, for now, we're going straight to trying shared.relish.pl, because Google searching and // signed-metadata verification and signed-code verification aren't implemented yet. } if currentVersion == "" { // Now try shared.relish.pl usingCentralRepo = true hostURL = "http://shared.relish.pl" // Read remote metadata file. Store it locally if its date is >= the local shared artifact metadata date. currentVersion, err = fetchArtifactMetadata(hostURL, originAndArtifactPath, metadataDate, sharedCurrentVersion, sharedReplicaMetadataFilePath) if err != nil { return // We really couldn't find and download metadata for this artifact anywhere we looked. Too bad. } } // metadataHostURL := hostURL // If we need to keep track of where we got the metadata from. if usingCentralRepo { hostURLs = []string{hostURL} } else { hostURLs = ldr.NonSearchedCodeHostURLs(originAndArtifactPath, hostURL) // NEW } if version == "" { version = currentVersion } // Version must now be a proper version number string, not "" var zipFileName string for _, hostURL = range hostURLs { zipFileContents, zipFileName, err = fetchArtifactZipFile(hostURL, originAndArtifactPath, version) if err == nil { break } // TODO consider logging the missed fetch and or developing a bad reputation for the host. } if zipFileContents == nil { err = fmt.Errorf("Search of Internet did not find relish software artifact '%s'", originAndArtifactPath) return } artifactKnownToBeReplica = true // Unzip the artifact into the proper local directory tree // TODO TODO Really don't know the artifact version here in some case, (in case there was nothing // not even a metadata.txt file locally, and no version was specified on command line) so // we don't have the correct path for artifactVersionDir known yet in that case !!! // WE DO KNOW IT HAS TO BE A SHARED REPLICASE DIR PATH however. versionStr := "/v" + version artifactVersionDir = ldr.RelishRuntimeLocation + "/shared/relish/replicas/" + originAndArtifactPath + versionStr //gos.MkdirAll(name string, perm FileMode) error var perm os.FileMode = 0777 err = gos.MkdirAll(artifactVersionDir, perm) if err != nil { return } zipFilePath := ldr.RelishRuntimeLocation + "/shared/relish/replicas/" + originAndArtifactPath + "/" + zipFileName err = gos.WriteFile(zipFilePath, zipFileContents, perm) if err != nil { return } // Open an artifact version zip archive for reading. var srcZipFileContents []byte srcZipFileContents, err = zip_util.ExtractFileFromZipFileContents(zipFileContents, "artifactVersionContents.zip") if err != nil { return } ///////////////////////////////////////////////////////////////////////////////////// // Verify the signed contents using digital signature verification. var sharedRelishPublicKeyCertificateBytes []byte var sharedRelishPublicKeyCertificate string var installationSharedRelishPublicKeyCert string var originPublicKeyCertificateBytes []byte var originPublicKeyCertificate string var signatureBytes []byte var signature string sharedRelishPublicKeyCertificateBytes, err = zip_util.ExtractFileFromZipFileContents(zipFileContents, "sharedRelishPublicKeyCertificate.pem") if err != nil { return } sharedRelishPublicKeyCertificate = strings.TrimSpace(string(sharedRelishPublicKeyCertificateBytes)) originPublicKeyCertificateBytes, err = zip_util.ExtractFileFromZipFileContents(zipFileContents, "originPublicKeyCertificate.pem") if err != nil { return } originPublicKeyCertificate = strings.TrimSpace(string(originPublicKeyCertificateBytes)) signatureBytes, err = zip_util.ExtractFileFromZipFileContents(zipFileContents, "signatureOfArtifactVersionContents.pem") if err != nil { return } signature = strings.TrimSpace(string(signatureBytes)) installationSharedRelishPublicKeyCert, err = crypto_util.GetPublicKeyCert("origin", "shared.relish.pl2012") if err != nil { // Could not find shared relish pl public key cert in keys/public directory of this relish project directory. // Try downloading the public key from http://shared.relish.pl and installing it in the project directory. var cert string cert, err = FetchSharedRelishPublicKeyCert() if err != nil { return } err = crypto_util.StorePublicKeyCert("origin", "shared.relish.pl2012", cert) if err != nil { return } installationSharedRelishPublicKeyCert, err = crypto_util.GetPublicKeyCert("origin", "shared.relish.pl2012") if err != nil { return } } installationSharedRelishPublicKeyCert = strings.TrimSpace(installationSharedRelishPublicKeyCert) if err != nil { return } // Is the shared relish public key cert in the artifact zip file identical to the cert that came with my // relish distribution? If not, panic. if sharedRelishPublicKeyCertificate != installationSharedRelishPublicKeyCert { err = fmt.Errorf("Did not install downloaded artifact because shared.relish.pl2012 public key certificate\n"+ "in artifact %s (v%s) downloaded from %s\n"+ "is different than shared.relish.pl2012 public key certificate in this relish installation.\n", originAndArtifactPath, version, hostURL) return } // Validate that shared.relish.pl2012 public key is signed properly, obtaining the shared.relish.pl2012 publicKeyPEM. sharedRelishPublicKey := crypto_util.VerifiedPublicKey("", sharedRelishPublicKeyCertificate, "origin", "shared.relish.pl2012") if sharedRelishPublicKey == "" { err = errors.New("Invalid shared.relish.pl2012 public key certificate.") return } // Validate the artifact-publishing origin's public key cert slashPos := strings.Index(originAndArtifactPath, "/") originId := originAndArtifactPath[:slashPos] originPublicKey := crypto_util.VerifiedPublicKey(sharedRelishPublicKey, originPublicKeyCertificate, "origin", originId) if originPublicKey == "" { err = fmt.Errorf("Did not install downloaded artifact because %s public key certificate\n"+ "in artifact %s (v%s) downloaded from %s\n"+ "is invalid.\n", originId, originAndArtifactPath, version, hostURL) return } signedContent := zipFileName + "_|_" + string(srcZipFileContents) if !crypto_util.Verify(originPublicKey, signature, signedContent) { err = fmt.Errorf("Did not install downloaded artifact because artifact version content\n"+ "in artifact %s (v%s) downloaded from %s\n"+ "does not match (was not verified by) its digital signature.\n", originAndArtifactPath, version, hostURL) return } // Woohoo! Contents are verified. // ///////////////////////////////////////////////////////////////////////////////////// // // Write them to relish installation shared code directory tree. // Note: Assuming the artifactVersionContents.zip file starts with src/ pkg/ doc/ etc not with v0002/ err = zip_util.ExtractZipFileContents(srcZipFileContents, artifactVersionDir) if err != nil { return } Log(ALWAYS_, "Downloaded %s (v%s) from %s\n", originAndArtifactPath, version, hostURL) } if !artifactAlreadyLoaded { // Read built.txt from the artifact version directory builtFilePath := artifactVersionDir + "/built.txt" var builtFileContents []byte _, statErr := gos.Stat(builtFilePath) if statErr == nil { builtFileContents, err = gos.ReadFile(builtFilePath) if err != nil { return } artifactsVersionsStrs := strings.Fields(string(builtFileContents)) n := len(artifactsVersionsStrs) for i := 0; i < n; i += 2 { artifactPath := artifactsVersionsStrs[i] artifactVersion := artifactsVersionsStrs[i+1] alreadyDesiredVersion, versionFound := ldr.LoadedArtifacts[artifactPath] if versionFound { if artifactVersion != alreadyDesiredVersion { Log(ALWAYS_, "Using v%s of %s. %s (v%s) may prefer v%s of %s.\n", alreadyDesiredVersion, artifactPath, originAndArtifactPath, version, artifactVersion, artifactPath) } } else { // Tell the loader to prefer the version of the other artifact // that the being-loaded artifact built.txt file specifies. ldr.LoadedArtifacts[artifactPath] = artifactVersion } } } else if !os.IsNotExist(statErr) { err = fmt.Errorf("Can't stat '%s': %v\n", builtFilePath, statErr) return } // else there is no built.txt file. Accept that for now // and subsequently load the current versions of artifacts where no other version is preferred. } //////////////////////////////////////////////////////////////////////// // TODO // Load all the code files in the package of the artifact. // compile files if necessary? // // record the version of the artifact and package in the loader's registry of loaded packages and artifacts. packageSourcePath := artifactVersionDir + "/src/" + packagePath packageCompiledPath := artifactVersionDir + "/pkg/" + packagePath // Read the filenames of source files etc. in the /src/... package directory var sourceDirFile *os.File sourceDirFile, err = gos.Open(packageSourcePath) if err != nil { return } defer sourceDirFile.Close() var filenames []string filenames, err = sourceDirFile.Readdirnames(-1) if err != nil { return } // Create /pkg/ dir tree if does not exist already _, statErr := gos.Stat(packageCompiledPath) if statErr != nil { if !os.IsNotExist(statErr) { err = fmt.Errorf("Can't stat relish intermediate-code directory '%s': %v\n", packageCompiledPath, statErr) return } var perm os.FileMode = 0777 err = gos.MkdirAll(packageCompiledPath, perm) if err != nil { return } } ldr.LoadedArtifacts[originAndArtifactPath] = version ldr.LoadedArtifactKnownToBePublished[originAndArtifactPath] = strings.Contains(artifactVersionDir, "/shared/relish/artifacts/") ldr.LoadedArtifactKnownToBeReplica[originAndArtifactPath] = artifactKnownToBeReplica Log(LOAD2_, "ldr.LoadedArtifactKnownToBePublished[%s]=%v\n", originAndArtifactPath, ldr.LoadedArtifactKnownToBePublished[originAndArtifactPath]) Log(LOAD2_, "ldr.LoadedArtifactKnownToBeReplica[%s]=%v\n", originAndArtifactPath, ldr.LoadedArtifactKnownToBeReplica[originAndArtifactPath]) Logln(LOAD_, "artifactVersionDir="+artifactVersionDir) ldr.LoadedArtifactKnownToBeLocal[originAndArtifactPath] = !(ldr.LoadedArtifactKnownToBePublished[originAndArtifactPath] || artifactKnownToBeReplica) if relish.DatabaseURI() == "" { dbDirPath := ldr.databaseDirPath(originAndArtifactPath) var perm os.FileMode = 0777 err = gos.MkdirAll(dbDirPath, perm) if err != nil { return } dbFilePath := dbDirPath + "/" + ldr.DatabaseName relish.SetDatabaseURI(dbFilePath) // TODO NOT TRUE AT ALL YET // This can be overridden with a statement in the program, as long as a persistence op has not been used first. } // Collect a map of file nodes to the root of the filename. We will be passing this to the generator to generate runtime // code for the whole package all at once. // astFileNodes := make(map[*ast.File]string) for _, filename := range filenames { var sourceFound bool var pickledFound bool if strings.HasSuffix(filename, ".rel") { // consider only the relish source files in the dir. // This is actually quite controversial, since it means that source code MUST be present // or we won't bother looking for the compiled file to load. // This is a somewhat political opinionated decision. Will have to be seriously mulled if not pondered. // TODO add in here the on-demand compilation as found in relish.go // Doing it NOW!! sourceFilePath := packageSourcePath + "/" + filename fileNameRoot := filename[:len(filename)-4] pickleFilePath := packageCompiledPath + "/" + fileNameRoot + ".rlc" sourceFileInfo, statErr := gos.Stat(sourceFilePath) if statErr != nil { if !os.IsNotExist(statErr) { err = fmt.Errorf("Can't stat relish source file '%s': %v\n", sourceFilePath, statErr) return } } else { sourceFound = true } pickleFileInfo, statErr := gos.Stat(pickleFilePath) if statErr != nil { if !os.IsNotExist(statErr) { err = fmt.Errorf("Can't stat relish intermediate-code file '%s': %v\n", pickleFilePath, statErr) return } } else { pickledFound = true } var parseNeeded bool if sourceFound { if pickledFound { if sourceFileInfo.ModTime().After(pickleFileInfo.ModTime()) { parseNeeded = true } } else { parseNeeded = true } } else if !pickledFound { err = fmt.Errorf("Error: Found neither relish source file '%s' nor intermediate-code file '%s'.\n", sourceFilePath, pickleFilePath) return } var fileNode *ast.File if parseNeeded { var fset = token.NewFileSet() fileNode, err = parser.ParseFile(fset, sourceFilePath, nil, parserDebugMode) if err != nil { err = fmt.Errorf("Error parsing file '%s': %v\n", sourceFilePath, err) return } err = ast.Pickle(fileNode, pickleFilePath) if err != nil { err = fmt.Errorf("Error pickling file '%s': %v\n", sourceFilePath, err) return } } else { // read the pickled (intermediate-code) file fileNode, err = ast.Unpickle(pickleFilePath) if err != nil { err = fmt.Errorf("Error unpickling file '%s': %v\n", pickleFilePath, err) return } } err = ldr.ensureImportsAreLoaded(fileNode) if err != nil { return } astFileNodes[fileNode] = fileNameRoot // gen = generator.NewGenerator(fileNode, fileNameRoot) // TODO NOW add a isLocal =ldr.LoadedArtifactKnownToBeLocal[originAndArtifactPath] // argument so that we can flag the RPackage object as local or shared. // gen.GenerateCode() if packageIdentifier != fileNode.Name.Name { err = fmt.Errorf("\nThe origin, artifact, or package metadata at top of source code file\n'%s'\ndoes not match the package directory path where the file resides.\n", sourceFilePath) return } if parseNeeded { if !ldr.quiet { Log(ALWAYS_, "Compiled %s\n", sourceFilePath) } } } // end of if it is a code file. } // end of loop over each file in the package. if len(astFileNodes) > 0 { gen = generator.NewGenerator(astFileNodes) gen.GenerateCode() } native_methods.WrapNativeMethods(packageIdentifier) // Check if package has native methods, if so, make RMethod wrappers. ldr.LoadedPackages[packageIdentifier] = version delete(ldr.PackagesBeingLoaded, packageIdentifier) if !ldr.quiet { Log(ALWAYS_, "Loaded %s\n", packageCompiledPath) } return }
/* Remove the existing shared copy of the version of the artifact (in a failsafe manner). Copy source code for the version of the package to the shared sourcecode tree. Create a zip file of the version of the artifact. Wrap that with another zip file which will eventually contain a certificate of the origin's public key, and a signature of (the SHA256 hash of) the inner zip file contents. Name the outer zip file by an unambiguous name corresponding to the version of the artifact from the origin. Check to see if there is a metadata file in the shared directory for the artifact. If no metadata file in shared, add it. Metadata file name should again be fully qualified unambiguous name, to ensure findability by Google search. Or the content should contain a standard findable title like "Relish Artifact Metadata". Note that the metadata.txt file should eventually also include, below its meaningful text content, a certificate of the origin's public key, and a signature of (the SHA256 hash of) the meaningful content. Should copy local artifact version directory tree that was published to create the next version to continue work on. Note about signed code plans ============================ The plan is to have every copy of relish include the public key of shared.relish.pl. This key can be directly compared manually at any time with the public key published at the site shared.relish.pl. Each code-origin owner who wants to officially publish relish code should register their origin at shared.relish.pl and should receive back both 1) a certificate (signed by shared.relish.pl and verifiable with shared.relish.pl's public key) where such certificate attests to the association between another public key and the origin name. 2) The actual origin public key 3) A corresponding private key. The origin owner should keep their private key secret but install it in a standard place within their relish development environment so that their relish instance can use it to sign code. The origin owner should also keep their certificate of their public key installed at a standard place in their relish development environment, so they can include that cert in signed-code outer zip files. The certified public key should actually be published at a standard place in the canonical server for the origin, and also at shared.relish.pl. This is so that such a key can periodically be verified as being a currently valid one. Note: If the private key is stolen, someone else can produce code signed as coming from your origin, so maybe the same password used to sign up to register your origin should serve as a decryption key for a symmetrically encrypted version of the private key. So you would be prompted to enter the password when publishing (and signing) some code. If not using encrypted private key, then if it is discovered that the key has been stolen (e.g. some code fraudulently claiming to be from the origin is discovered), then a solution would be to apply for a new public key for the origin, and re-publish all legitimate code signed by the new key. Periodically, a imported-code-using instance of relish should re-verify (at the canonical server or shared.relish.pl) that the public key that is signing each of their imported artifacts is still the valid public key for the origin. Perhaps shared.relish.pl can contain a timestamp of when the most recent re-keying incident happened, and relish instances can periodically check that to see if re-verification of origin public-keys, and possible redownloading of re-signed code artifacts is necessary. */ func PublishSourceCode(relishRoot string, originAndArtifact string, version string) (err error) { slashPos := strings.Index(originAndArtifact, "/") originId := originAndArtifact[:slashPos] // Obtain the private key for the origin that is publishing. originPrivateKey, err := crypto_util.GetPrivateKey("origin", originId) if err != nil { return } // Obtain the public key certificate for the origin that is publishing. originPublicKeyCertificate, err := crypto_util.GetPublicKeyCert("origin", originId) if err != nil { return } // Obtain the public key certificate of shared.relish.pl2012 sharedRelishPublicKeyCertificate, err := crypto_util.GetPublicKeyCert("origin", "shared.relish.pl2012") if err != nil { return } // Validate that it is signed properly, obtaining the shared.relish.pl2012 publicKeyPEM. sharedRelishPublicKey := crypto_util.VerifiedPublicKey("", sharedRelishPublicKeyCertificate, "origin", "shared.relish.pl2012") if sharedRelishPublicKey == "" { err = errors.New("Invalid shared.relish.pl2012 public key certificate.") return } // Do a quick validation of publishing origin's public key cert originPublicKey := crypto_util.VerifiedPublicKey(sharedRelishPublicKey, originPublicKeyCertificate, "origin", originId) if originPublicKey == "" { err = errors.New("Invalid " + originId + " public key certificate.") return } // prompt for the publishing origin's private key password. var buf *bufio.Reader = bufio.NewReader(os.Stdin) fmt.Print("Enter code-origin administration password:"******"/artifacts/" + originAndArtifact + "/" sharedArtifactPath := relishRoot + "/shared/relish/artifacts/" + originAndArtifact + "/" // Check if metadata.txt file exists in shared. If not, create it by copying local metadata.txt file sharedMetadataPath := sharedArtifactPath + "metadata.txt" _, err = gos.Stat(sharedMetadataPath) if err != nil { if os.IsNotExist(err) { err = gos.MkdirAll(sharedArtifactPath, 0777) if err != nil { fmt.Printf("Error making shared artifact directory %s: %s\n", sharedArtifactPath, err) return } localMetadataPath := localArtifactPath + "metadata.txt" var content []byte content, err = gos.ReadFile(localMetadataPath) if err != nil { return } err = gos.WriteFile(sharedMetadataPath, content, 0666) if err != nil { return } } else { fmt.Printf("Can't stat '%s' : %v\n", sharedArtifactPath+"metadata.txt", err) return } } // Note. This does not update the shared metadata.txt file from the local if the shared metadata.txt // file already existed. // versionPath := "v" + version sharedArtifactVersionPath := sharedArtifactPath + versionPath _, err = gos.Stat(sharedArtifactVersionPath) foundVersionShared := false if err != nil { if !os.IsNotExist(err) { fmt.Printf("Can't stat directory '%s' : %v\n", sharedArtifactVersionPath, err) return } } else { foundVersionShared = true } if foundVersionShared { fmt.Printf("%s already exists. Cannot republish the same version.\n", sharedArtifactVersionPath) return } localSrcDirPath := localArtifactPath + versionPath + "/src" srcDirPath := sharedArtifactVersionPath + "/src" localDocDirPath := localArtifactPath + versionPath + "/doc" docDirPath := sharedArtifactVersionPath + "/doc" // mkdir the version of the shared directory err = gos.MkdirAll(sharedArtifactVersionPath, 0777) if err != nil { fmt.Printf("Error making shared artifact version directory %s: %s\n", sharedArtifactVersionPath, err) return } // Copy source code directory tree to "shared/relish/artifacts" tree root. err = copySrcDirTree(localSrcDirPath, srcDirPath) if err != nil { fmt.Printf("Error copying local src dir to create %s: %s\n", srcDirPath, err) return } _, statErr := gos.Stat(localDocDirPath) if statErr == nil { err = copyDocDirTree(localDocDirPath, docDirPath) if err != nil { fmt.Printf("Error copying local doc dir to create %s: %s\n", docDirPath, err) return } } // TBD // Zip the source and docs! srcZipFilePath := sharedArtifactVersionPath + "/artifactVersionContents.zip" err = zipSrcAndDocDirTrees(srcDirPath, docDirPath, srcZipFilePath) if err != nil { fmt.Printf("Error zipping %s and %s: %s\n", srcDirPath, docDirPath, err) return } // Now have to sign it and put into an outer zip file. err = signZippedSrc(srcZipFilePath, originPrivateKey, originPrivateKeyPassword, originPublicKeyCertificate, sharedRelishPublicKeyCertificate, sharedArtifactPath, originAndArtifact, version) if err != nil { fmt.Printf("Error signing %s: %s\n", srcZipFilePath, err) return } err = gos.Remove(srcZipFilePath) if err != nil { fmt.Printf("Error removing %s: %s\n", srcZipFilePath, err) return } return }