{"id":19048996,"url":"https://github.com/tcdowney/cred_hubble","last_synced_at":"2025-07-09T10:37:49.647Z","repository":{"id":62556378,"uuid":"104696560","full_name":"tcdowney/cred_hubble","owner":"tcdowney","description":"Unofficial Ruby Client for Cloud Foundry CredHub Credential Store 🔭","archived":false,"fork":false,"pushed_at":"2017-11-03T02:40:40.000Z","size":67,"stargazers_count":2,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-04-26T19:22:24.231Z","etag":null,"topics":["cloud-foundry-credhub","credub-ruby-client","http-client","ruby"],"latest_commit_sha":null,"homepage":"http://www.rubydoc.info/gems/cred_hubble/CredHubble/Client","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tcdowney.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-09-25T02:53:49.000Z","updated_at":"2019-06-14T17:13:56.000Z","dependencies_parsed_at":"2022-11-03T06:00:26.076Z","dependency_job_id":null,"html_url":"https://github.com/tcdowney/cred_hubble","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tcdowney%2Fcred_hubble","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tcdowney%2Fcred_hubble/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tcdowney%2Fcred_hubble/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tcdowney%2Fcred_hubble/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tcdowney","download_url":"https://codeload.github.com/tcdowney/cred_hubble/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223764427,"owners_count":17198624,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cloud-foundry-credhub","credub-ruby-client","http-client","ruby"],"created_at":"2024-11-08T23:09:22.634Z","updated_at":"2024-11-08T23:09:23.276Z","avatar_url":"https://github.com/tcdowney.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CredHubble :full_moon_with_face::telescope::full_moon_with_face:\n\n[![Gem Version](https://badge.fury.io/rb/cred_hubble.svg)](https://badge.fury.io/rb/cred_hubble) [![Build Status](https://travis-ci.org/tcdowney/cred_hubble.svg?branch=master)](https://travis-ci.org/tcdowney/cred_hubble)\n\nUnofficial Ruby client for storing and fetching credentials from a [Cloud Foundry CredHub](https://github.com/cloudfoundry-incubator/credhub) credential store.\nThe goal of this gem is to make it easier for Ruby apps (Rails, Sinatra, etc.) deployed on Cloud Foundry to store and retrieve secrets (e.g. Rails session_token_base, database credentials, AWS keys, etc.).\nFor a more concrete example of usage, I've written a blog post on how one might [use CredHubble with a Rails app](https://downey.io/blog/securing-rails-credentials-cloud-foundry-credhub/).\n\nCredHubble is just something I work on in my spare time for fun and is not feature-complete, but it should get the job mostly done.\nIf you do end up using it and find any bugs or would like to see more functionality, feel free to [submit a PR](https://github.com/tcdowney/cred_hubble/pulls) or [log an issue](https://github.com/tcdowney/cred_hubble/issues).\n\nView the [usage](#usage) section to see what CredHub endpoints the gem currently supports.\n\n## Installation\n\nTo install the latest release, add this line to your application's Gemfile:\n```ruby\ngem 'cred_hubble', '~\u003e 0.1.0'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install cred_hubble\n    \n## Usage\n\nCredHubble currently supports the following [CredHub endpoints](https://credhub-api.cfapps.io):\n\n* **[Client Creation and Authentication](#client-creation-and-authentication)**\n\n\n* **[GET Info](#get-info-and-get-health):** `/info`\n* **[GET Health](#get-info-and-get-health):** `/health`\n* **[GET Encryption Key Usage](#get-encryption-key-usage):** `/api/v1/key-usage`\n\n\n* **[GET Credential by ID](#get-credential-by-id):** `/api/v1/data/\u003ccredential-id\u003e`\n* **[GET Credentials by Name](#get-credentials-by-name):** `/api/v1/data?name=\u003ccredential-name\u003e`\n* **[PUT Credential](#put-credential):** `/api/v1/data`\n* **[DELETE Credential by Name](#delete-credential-by-name):** `/api/v1/data`\n* **[POST Interpolate Credentials](#post-interpolate-credentials):** `/api/v1/interpolate`\n\n\n* **[GET Permissions by Credential Name](#get-permissions-by-credential-name):** `/api/v1/permissions?credential_name=\u003ccredential-name\u003e`\n* **[POST Add Permissions](#post-add-permissions):** `/api/v1/permissions`\n* **[DELETE Delete Permissions](#delete-delete-permissions):** `/api/v1/permissions?credential_name=\u003ccredential-name\u003e\u0026actor=\u003cactor\u003e`\n\n### Client Creation and Authentication\n\nTo call endpoints that require authentication, you will need to authenticate with either an oAuth2 bearer token 'Authorization' header or with certificate-based [mutual TLS](https://en.wikipedia.org/wiki/Mutual_authentication) (mTLS).\nHere are some examples:\n\n#### Authenticating with an oAuth2 header\n```ruby\n\u003e auth_header    = 'eyJhbGc.....OiJSUzI1NiIsI' # omit any 'bearer' portion\n\u003e credhub_client = CredHubble::Client.new_from_token_auth(\n                     host: 'credhub.your-cloud-foundry.com',\n                     port: '8844',\n                     auth_header_token: auth_header\n                   )\n           \n\u003e credential = credhub_client.credential_by_id('f8d5a201-c3b9-48ae-8bc4-3b86b42210a1')\n  =\u003e #\u003cCredHubble::Resources::ValueCredential:0x0055f3811a5958 ...\n```\n\n#### Authenticating with a client cert and key over mutual TLS\nA typical Cloud Foundry application using CredHub will have access to two environment variables that contain these paths:\n* `ENV['CF_INSTANCE_CERT']`\n* `ENV['CF_INSTANCE_KEY']`\n\nCredHub's CA certificate should already have been placed in the app instance's trusted cert store by Diego.\n\n```ruby\n\u003e client_cert_path = '/etc/cf-instance-credentials/instance.crt' # ENV['CF_INSTANCE_CERT']\n\u003e client_key_path  = '/etc/cf-instance-credentials/instance.key' # ENV['CF_INSTANCE_KEY']\n\u003e credhub_client   = CredHubble::Client.new_from_mtls_auth(\n                       host: 'credhub.your-cloud-foundry.com',\n                       port: '8844',\n                       client_cert_path: client_cert_path,\n                       client_key_path: client_key_path\n                     )\n           \n\u003e credential = credhub_client.credential_by_id('f8d5a201-c3b9-48ae-8bc4-3b86b42210a1')\n  =\u003e #\u003cCredHubble::Resources::ValueCredential:0x0055f3811a5958 ...\n```\n\n#### Specifying the CredHub CA certificate\nIf your CredHub server is using a self-signed (or otherwise non-trusted by your system) certificate you can supply CredHubble with the path to a local copy of the signing CA certificate.\n\n```ruby\n\u003e auth_header     = 'eyJhbGc.....OiJSUzI1NiIsI' # omit any 'bearer' portion\n\u003e credhub_ca_path = '/some/path/certs/credhub_ca.crt'\n\u003e credhub_client  = CredHubble::Client.new_from_token_auth(\n                      host: 'credhub.your-cloud-foundry.com',\n                      port: '8844',\n                      auth_header_token: auth_header,\n                      ca_path: credhub_ca_path\n                    )\n\n\u003e credential = credhub_client.credential_by_id('f8d5a201-c3b9-48ae-8bc4-3b86b42210a1')\n  =\u003e #\u003cCredHubble::Resources::ValueCredential:0x0055f3811a5958 ...\n```\n\n### Supported Actions\n\n### GET Info and GET Health\nTo try out the unauthenticated `info` and `health` endpoints, just do the following in your Ruby console:\n\n```ruby\n\u003e credhub_client = CredHubble::Client.new(host: 'credhub.your-cloud-foundry.com', port: '8844')\n           \n\u003e info = credhub_client.info\n  =\u003e #\u003cCredHubble::Resources::Info:0x00007fb36497a490 ...\n  \n\u003e info.auth_server.url\n  =\u003e \"https://uaa.service.cf.internal:8443\"\n  \n\u003e health = credhub_client.health\n  =\u003e #\u003cCredHubble::Resources::Health:0x00007fb3648f0218 ...\n  \n\u003e health.status\n  =\u003e \"UP\"\n```\n\nFor accessing endpoints that require authentication, simply create an authenticated client using one of the [authentication methods above](#authentication).\n\n### GET Credential by ID\nThe `credential_by_id` method retrieves a single Credential resource from CredHub by ID.\n\n```ruby\n\u003e credhub_client.credential_by_id('f297f736-dad2-4450-a7da-d3ff99f2030d')\n  =\u003e #\u003cCredHubble::Resources::ValueCredential:0x0055f3811a5958 ...\n```\n\n### GET Credentials by Name\nRetrieves a collection of Credentials from CredHub for the given name. The `credentials_by_name` method will return all stored versions of the credential by default.\nYou can retrieve only the most recent version of the credential using the `current` option, or specify the number of versions to fetch with the `versions` option.\n\n```ruby\n\u003e credentials = credhub_client.credentials_by_name('/admin-user-password')\n  =\u003e #\u003cCredHubble::Resources::CredentialCollection:0x00007f @data=[#\u003cCredHubble::Resources::PasswordCredential:0x00004a ...\n\u003e credentials.count\n  =\u003e 3\n\u003e credentials.map(\u0026:id)\n  =\u003e [\"5298e0e4-c3f5-4c73-a156-9ffce4c137f5\", \"6980ec59-c7e6-449a-b525-298648cfe6a7\", \"3e709d6e-585c-4526-ac0d-fe99316f2255\"]\n  \n\u003e credentials = credhub_client.credentials_by_name('/admin-user-password', versions: 2)  \n\u003e credentials.count\n  =\u003e 2\n\u003e credentials.map(\u0026:id)\n  =\u003e [\"5298e0e4-c3f5-4c73-a156-9ffce4c137f5\", \"6980ec59-c7e6-449a-b525-298648cfe6a7\"]\n  \n\u003e credentials = credhub_client.credentials_by_name('/admin-user-password', current: true)\n  =\u003e #\u003cCredHubble::Resources::CredentialCollection:0x00007f @data=[#\u003cCredHubble::Resources::PasswordCredential:0x00004a ...\n\u003e credentials.count\n  =\u003e 1\n\u003e credentials.map(\u0026:id)\n  =\u003e [\"5298e0e4-c3f5-4c73-a156-9ffce4c137f5\"]\n```\n\nMost times, though, you'll just want to grab the value of the most current version of a credential. This is where the `current_credential_value` method comes in.\nHere's what that might look like for the example above:\n\n```ruby\n\u003e credhub_client.current_credential_value('/admin-user-password')\n  =\u003e \"8mn6LSLzJqhVxnqYCCXUxUADdj8XneYP\"\n```\n\n### PUT Credential\nYou can create new Credentials using the `put_credential` method. If you wish to replace an already existing Credential, simply pass\n`overwrite: true` to the method and CredHub will create a new version of the Credential. Previous versions can be retrieved by using\nthe `credentials_by_name` method.\n\n```ruby\n\u003e credential = CredHubble::Resources::UserCredential.new(\n                    name: '/foundry-fred-user',\n                    value: {username: 'foundy_fred', password: 's3cr3t'}\n               )   \n  =\u003e #\u003cCredHubble::Resources::UserCredential:0x00007fb322caf3f0 @name=\"/foundry-fred-user\", @value=#\u003cCredHubble::Resources::UserValue ...\n  \n\u003e credhub_client.put_credential(credential)\n  =\u003e #\u003cCredHubble::Resources::UserCredential:0x00007fb322d676d0\n        @name=\"/foundry-fred-user\",\n        @value=#\u003cCredHubble::Resources::UserValue:0x00007fb322d67478\n                  @username=\"foundy_fred\",\n                  @password=\"s3cr3t\",\n                  @password_hash=\"$6$WwMLCRDr$Br54U0EnWD.A5i1EV9Cc7P16ZdjIBk0fFiYKghfOjW1MvL.vaXhWua.eGIbe0ziQIEP4s2OcGQpEEsc9ClFuA0\"\u003e,\n                  @id=\"92775889-71e0-41d1-a44c-93eb8fc5161a\",\n                  @type=\"user\",\n                  @version_created_at=\"2017-10-06T05:10:57Z\"\u003e\n             \n\u003e credential.value.password = 'foo bar'\n  =\u003e \"foo bar\"\n  \n\u003e credhub_client.put_credential(credential, overwrite: true)\n  =\u003e #\u003cCredHubble::Resources::UserCredential:0x00007fb322d676d0\n        @name=\"/foundry-fred-user\",\n        @value=#\u003cCredHubble::Resources::UserValue:0x00007fb322d67478\n                  @username=\"foundy_fred\",\n                  @password=\"foo bar\",\n                  @password_hash=\"$6$WNAIgDrf$/.DxIfIg.8W6ZaIRjrjlOWS8FenigeWtswWr/D9edMbmSReYCzgG6VVdcdaftenq5VED3C8MJNVtDnNLF86SD.\"\u003e,\n                  @id=\"292ae24c-d7a3-4d8b-86a2-43630b83bafb\",\n                  @type=\"user\",\n                  @version_created_at=\"2017-10-06T05:11:43Z\"\u003e\n````\n\nBy default, only the creator of a Credential has access to read, write, delete, view its ACL, or updates its ACL. If you wish to\ngrant other parties various permissions for a given Credential, the `put_credential` method takes an optional `additional_permissions` array.\n\n```ruby\n\u003e credential = CredHubble::Resources::UserCredential.new(\n                    name: '/foundry-fred-user',\n                    value: {username: 'foundy_fred', password: 's3cr3t'}\n               )   \n  =\u003e #\u003cCredHubble::Resources::UserCredential:0x00007fb322caf3f0 @name=\"/foundry-fred-user\", @value=#\u003cCredHubble::Resources::UserValue ...\n  \n\u003e permission = CredHubble::Resources::Permission.new(\n                 actor: 'uaa-user:82f8ff1a-fcf8-4221-8d6b-0a1d579b6e47',\n                 operations: ['write', 'read']\n               )\n  =\u003e #\u003cCredHubble::Resources::Permission:0x00007f @actor=\"uaa-user:82f8ff1a-fcf8-4221-8d6b-0a1d579b6e47\", @operations=[\"write\", \"read\"]\u003e\n  \n\u003e credhub_client.put_credential(credential, additional_permissions: [permission])\n  =\u003e #\u003cCredHubble::Resources::UserCredential:0x00007fb322d676d0 ...\n````\n\n### DELETE Credential by Name\nThe `delete_credential_by_name` method allows you to delete all versions of a Credential for the given name.\n\n```ruby\n\u003e credentials = credhub_client.credentials_by_name('/admin-user-password')\n  =\u003e #\u003cCredHubble::Resources::CredentialCollection:0x00007f @data=[#\u003cCredHubble::Resources::PasswordCredential:0x00004a ...\n\u003e credentials.count\n  =\u003e 3\n  \n\u003e credhub_client.delete_credential_by_name('/admin-user-password')  \n  =\u003e true\n\u003e credhub_client.credentials_by_name('/admin-user-password')\n  =\u003e CredHubble::Http::NotFoundError: status: 404, body: {\"error\":\"The request could not be completed ...\n````\n\n### POST Interpolate Credentials\nCloud Foundry applications traditionally access the credentials for any bound service instances through a `VCAP_SERVICES` environment variable.\nNowadays, however, some Service Brokers are CredHub aware and may choose to store service instance credentials in CredHub.\nApps bound to said services would only see `\"credhub-ref\"` key in place of actual credentials for that service instance. Here's an example `VCAP_SERVICES`:\n\n```json\n{\n  \"grid-config\":[\n    {\n      \"credentials\":{\n        \"credhub-ref\":\"/grid-config/users/kflynn\"\n      },\n      \"label\":\"grid-config\",\n      \"name\":\"config-server\",\n      \"plan\":\"digital-frontier\",\n      \"provider\":null,\n      \"syslog_drain_url\":null,\n      \"tags\":[\n        \"configuration\",\n        \"biodigital-jazz\"\n      ],\n      \"volume_mounts\":[]\n    }\n  ],\n  \"encomSQL\":[\n    {\n      \"credentials\":{\n        \"credhub-ref\":\"/encomSQL/db/users/63f7b900-982f-4f20-9213-6d270c3c58ea\"\n      },\n        \"label\":\"encom-db\",\n      \"name\":\"encom-enterprise-db\",\n      \"plan\":\"enterprise\",\n      \"provider\":null,\n      \"syslog_drain_url\":null,\n      \"tags\":[\n        \"database\",\n        \"sql\"\n      ],\n      \"volume_mounts\":[]\n    }\n  ]\n}\n```\n\nFortunately, CredHub supports an \"interpolate\" endpoint which allows an app to populate these values wholesale.\nHere's how a CF application might use CredHubble's `interpolate_credentials` method to do that via mTLS authentication:\n\n```ruby\n\u003e client_cert_path = ENV['CF_INSTANCE_CERT']\n\u003e client_key_path  = ENV['CF_INSTANCE_KEY']\n\u003e credhub_client   = CredHubble::Client.new_from_mtls_auth(\n                       host: 'credhub.your-cloud-foundry.com',\n                       port: '8844',\n                       client_cert_path: client_cert_path,\n                       client_key_path: client_key_path\n                     )\n           \n\u003e interpolated_services_json = credhub_client.interpolate_credentials(ENV['VCAP_SERVICES'])\n  =\u003e '{\n       \"grid-config\":[\n         {\n           \"credentials\":{\n             \"username\":\"kflynn\",\n             \"password\":\"FlynnLives\"\n           },\n           \"label\":\"grid-config\",\n           \"name\":\"config-server\",\n           \"plan\":\"digital-frontier\",\n           \"provider\":null,\n           \"syslog_drain_url\":null,\n           \"tags\":[\n             \"configuration\",\n             \"biodigital-jazz\"\n           ],\n           \"volume_mounts\":[]\n         }\n       ],\n       \"encomSQL\":[\n         {\n           \"credentials\":{\n             \"username\":\"grid-db-user\",\n             \"password\":\"p4ssw0rd\"\n           },\n           ... abridged ...\n         }\n       ]\n     }'\n```\n\n### GET Permissions by Credential Name\n\nYou can use the `permissions_by_credential_name` method to view the list of permissions for a given Credential.\n\n```ruby\n\u003e credhub_client.permissions_by_credential_name('/credential-name')\n  =\u003e #\u003cCredHubble::Resources::PermissionCollection:0x00007fa231c12020\n        @credential_name=\"/credential-name\",\n        @permissions=[\n          #\u003cCredHubble::Resources::Permission:0x00007fa231c11f08\n              @actor=\"uaa-user:82f8ff1a-fcf8-4221-8d6b-0a1d579b6e47\",\n              @operations=[\"read\", \"write\", \"delete\"]\u003e,\n          #\u003cCredHubble::Resources::Permission:0x00007fa231c11e18\n              @actor=\"mtls-app:18f64563-bcfe-4c88-bf73-05c9ad3654c8\",\n              @operations=[\"read\"]\u003e,\n          #\u003cCredHubble::Resources::Permission:0x00007fa231c11d00\n              @actor=\"uaa-client:some_uaa_client\",\n              @operations=[\"read\", \"write\", \"delete\", \"read_acl\", \"write_acl\"]\u003e\n        ]\u003e\n```\n\n### POST Add Permissions\n\nYou can use the `add_permissions` method to add additional permissions to an existing Credential.\n\n```ruby\n\u003e credhub_client.permissions_by_credential_name('/my-awesome-credential').count\n  =\u003e 2\n  \n\u003e new_permission = CredHubble::Resources::Permission.new(actor: 'uaa-user:b2449249', operations: ['read'])\n\u003e new_permission_collection = CredHubble::Resources::PermissionCollection.new(\n                                credential_name: '/my-awesome-credential',\n                                permissions: [new_permission]\n                              )\n                       \n\u003e credhub_client.add_permissions(new_permission_collection)\n  =\u003e #\u003cCredHubble::Resources::PermissionCollection:0x00007fa231c12020\n        @credential_name=\"/my-awesome-credential\",\n        @permissions=[\n          #\u003cCredHubble::Resources::Permission:0x00007fa231c11f08\n              @actor=\"uaa-user:82f8ff1a-fcf8-4221-8d6b-0a1d579b6e47\",\n              @operations=[\"read\", \"write\", \"delete\"]\u003e,\n          #\u003cCredHubble::Resources::Permission:0x00007fa231c11e18\n              @actor=\"mtls-app:18f64563-bcfe-4c88-bf73-05c9ad3654c8\",\n              @operations=[\"read\"]\u003e,\n          #\u003cCredHubble::Resources::Permission:0x00007fa231c11d00\n              @actor=\"uaa-user:b2449249\",\n              @operations=[\"read\"]\u003e\n        ]\u003e\n        \n\u003e credhub_client.permissions_by_credential_name('/my-awesome-credential').count\n  =\u003e 3\n```\n\n### DELETE Delete Permissions\n\nYou can remove any permissions for a given actor from a credential with the `delete_permissions` method which takes a `credential_name` and `actor`.\n\n```ruby\n\u003e credhub_client.permissions_by_credential_name('/my-awesome-credential').count\n  =\u003e 3\n  \n\u003e credhub_client.delete_permissions('/my-awesome-credential', 'uaa-user:b2449249')\n  =\u003e true\n        \n\u003e credhub_client.permissions_by_credential_name('/my-awesome-credential').count\n  =\u003e 2\n```\n\n### GET Encryption Key Usage\n\nYou can fetch information about how many credentials are encrypted for the active (and inactive) encryption key\nusing the `key_usage` method. For more information, check out the official [CredHub docs](https://credhub-api.cfapps.io/#encryption-key-usage)\nfor this endpoint.\n\n```ruby\n\u003e encryption_key_usage = credhub_client.key_usage\n  =\u003e #\u003cCredHubble::Resources::KeyUsage:0x00007f8 ...\u003e\n  \n\u003e encryption_key_usage.active_key\n  =\u003e 9000\n        \n\u003e encryption_key_usage.inactive_keys\n  =\u003e 20\n  \n\u003e encryption_key_usage.unknown_keys\n  =\u003e 0\n```\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/tcdowney/cred_hubble.\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftcdowney%2Fcred_hubble","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftcdowney%2Fcred_hubble","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftcdowney%2Fcred_hubble/lists"}