diff --git a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
index 63857e4..b01033f 100644
--- a/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
+++ b/core/modules/rest/src/Plugin/rest/resource/EntityResource.php
@@ -2,11 +2,16 @@
 
 namespace Drupal\rest\Plugin\rest\resource;
 
+use Drupal\Core\Config\Entity\ConfigEntityType;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\rest\Plugin\ResourceBase;
 use Drupal\rest\ResourceResponse;
 use Drupal\rest\ModifiedResourceResponse;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
 use Symfony\Component\HttpKernel\Exception\HttpException;
@@ -30,6 +35,48 @@
 class EntityResource extends ResourceBase {
 
   /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * Constructs a Drupal\rest\Plugin\rest\resource\EntityResource object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param array $serializer_formats
+   *   The available serialization formats.
+   * @param \Psr\Log\LoggerInterface $logger
+   *   A logger instance.
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity type manager.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, EntityTypeManagerInterface $entity_type_manager) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
+    $this->entityTypeManager = $entity_type_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->getParameter('serializer.formats'),
+      $container->get('logger.factory')->get('rest'),
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
    * Responds to entity GET requests.
    *
    * @param \Drupal\Core\Entity\EntityInterface $entity
@@ -49,13 +96,16 @@ public function get(EntityInterface $entity) {
     $response = new ResourceResponse($entity, 200);
     $response->addCacheableDependency($entity);
     $response->addCacheableDependency($entity_access);
-    foreach ($entity as $field_name => $field) {
-      /** @var \Drupal\Core\Field\FieldItemListInterface $field */
-      $field_access = $field->access('view', NULL, TRUE);
-      $response->addCacheableDependency($field_access);
 
-      if (!$field_access->isAllowed()) {
-        $entity->set($field_name, NULL);
+    if ($entity instanceof FieldableEntityInterface) {
+      foreach ($entity as $field_name => $field) {
+        /** @var \Drupal\Core\Field\FieldItemListInterface $field */
+        $field_access = $field->access('view', NULL, TRUE);
+        $response->addCacheableDependency($field_access);
+
+        if (!$field_access->isAllowed()) {
+          $entity->set($field_name, NULL);
+        }
       }
     }
 
@@ -223,6 +273,10 @@ public function delete(EntityInterface $entity) {
    *   If validation errors are found.
    */
   protected function validate(EntityInterface $entity) {
+    // @todo Remove when https://www.drupal.org/node/2164373 is committed.
+    if (!$entity instanceof FieldableEntityInterface) {
+      return;
+    }
     $violations = $entity->validate();
 
     // Remove violations of inaccessible fields as they cannot stem from our
@@ -256,4 +310,29 @@ protected function getBaseRoute($canonical_path, $method) {
     return $route;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function availableMethods() {
+    $methods = parent::availableMethods();
+    if ($this->isConfigEntityResource()) {
+      // Currently only GET is supported for Config Entities.
+      // @todo Remove when supported https://www.drupal.org/node/2300677
+      $unsupported_methods = ['POST', 'PUT', 'DELETE', 'PATCH'];
+      $methods = array_diff($methods, $unsupported_methods);
+    }
+    return $methods;
+  }
+
+  /**
+   * Checks if this resource is for a Config Entity.
+   *
+   * @return bool
+   *   TRUE if the entity is a Config Entity, FALSE otherwise.
+   */
+  protected function isConfigEntityResource() {
+    $entity_type_id = $this->getPluginDefinition()['entity_type'];
+    return $this->entityTypeManager->getDefinition($entity_type_id) instanceof ConfigEntityType;
+  }
+
 }
diff --git a/core/modules/rest/src/RequestHandler.php b/core/modules/rest/src/RequestHandler.php
index 2aa3673..5bbb9be 100644
--- a/core/modules/rest/src/RequestHandler.php
+++ b/core/modules/rest/src/RequestHandler.php
@@ -42,6 +42,7 @@ public function handle(RouteMatchInterface $route_match, Request $request) {
       ->createInstance($plugin);
 
     // Deserialize incoming data if available.
+    /** @var \Symfony\Component\Serializer\SerializerInterface $serializer */
     $serializer = $this->container->get('serializer');
     $received = $request->getContent();
     $unserialized = NULL;
diff --git a/core/modules/rest/src/Tests/RESTTestBase.php b/core/modules/rest/src/Tests/RESTTestBase.php
index 092af7c..4afbf94 100644
--- a/core/modules/rest/src/Tests/RESTTestBase.php
+++ b/core/modules/rest/src/Tests/RESTTestBase.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\rest\Tests;
 
+use Drupal\Core\Config\Entity\ConfigEntityType;
 use Drupal\node\NodeInterface;
 use Drupal\simpletest\WebTestBase;
 
@@ -200,14 +201,14 @@ protected function entityCreate($entity_type) {
    * Required properties differ from entity type to entity type, so we keep a
    * minimum mapping here.
    *
-   * @param string $entity_type
-   *   The type of the entity that should be created.
+   * @param string $entity_type_id
+   *   The id of the type of entity that should be created.
    *
    * @return array
    *   An array of values keyed by property name.
    */
-  protected function entityValues($entity_type) {
-    switch ($entity_type) {
+  protected function entityValues($entity_type_id) {
+    switch ($entity_type_id) {
       case 'entity_test':
         return array(
           'name' => $this->randomMachineName(),
@@ -217,6 +218,11 @@ protected function entityValues($entity_type) {
             'format' => 'plain_text',
           )),
         );
+      case 'config_test':
+        return [
+          'id' => $this->randomMachineName(),
+          'label' => 'Test label',
+        ];
       case 'node':
         return array('title' => $this->randomString(), 'type' => 'resttest');
       case 'node_type':
@@ -236,8 +242,15 @@ protected function entityValues($entity_type) {
           'entity_id' => 'invalid',
           'field_name' => 'comment',
         ];
-
+      case 'taxonomy_vocabulary':
+        return [
+          'vid' => 'tags',
+          'name' => $this->randomMachineName(),
+        ];
       default:
+        if ($this->isConfigEntity($entity_type_id)) {
+          return $this->configEntityValues($entity_type_id);
+        }
         return array();
     }
   }
@@ -311,7 +324,7 @@ protected function curlExec($curl_options, $redirect = FALSE) {
   /**
    * Provides the necessary user permissions for entity operations.
    *
-   * @param string $entity_type
+   * @param string $entity_type_id
    *   The entity type.
    * @param string $operation
    *   The operation, one of 'view', 'create', 'update' or 'delete'.
@@ -319,8 +332,8 @@ protected function curlExec($curl_options, $redirect = FALSE) {
    * @return array
    *   The set of user permission strings.
    */
-  protected function entityPermissions($entity_type, $operation) {
-    switch ($entity_type) {
+  protected function entityPermissions($entity_type_id, $operation) {
+    switch ($entity_type_id) {
       case 'entity_test':
         switch ($operation) {
           case 'view':
@@ -365,9 +378,17 @@ protected function entityPermissions($entity_type, $operation) {
 
           default:
             return ['administer users'];
+        }
 
+      default:
+        if ($this->isConfigEntity($entity_type_id)) {
+          $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
+          if ($admin_permission = $entity_type->getAdminPermission()) {
+            return [$admin_permission];
+          }
         }
     }
+    return [];
   }
 
   /**
@@ -431,4 +452,49 @@ protected function assertResponseBody($expected, $message = '', $group = 'REST R
     return $this->assertIdentical($expected, $this->responseBody, $message ? $message : strtr('Response body @expected (expected) is equal to @response (actual).', array('@expected' => var_export($expected, TRUE), '@response' => var_export($this->responseBody, TRUE))), $group);
   }
 
+  /**
+   * Checks if an entity type id is for a Config Entity.
+   *
+   * @param string $entity_type_id
+   *   The entity type id to check.
+   *
+   * @return bool
+   *   TRUE if the entity is a Config Entity, FALSE otherwise.
+   */
+  protected function isConfigEntity($entity_type_id) {
+    return \Drupal::entityTypeManager()->getDefinition($entity_type_id) instanceof ConfigEntityType;
+  }
+
+  /**
+   * Provides an array of suitable property values for a config entity type.
+   *
+   * Config entities have some common keys that need to be created. Required
+   * properties differ among config entity types, so we keep a minimum mapping
+   * here.
+   *
+   * @param string $entity_type_id
+   *   The id of the type of entity that should be created.
+   *
+   * @return array
+   *   An array of values keyed by property name.
+   */
+  protected function configEntityValues($entity_type_id) {
+    $entity_type = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
+    $keys = $entity_type->getKeys();
+    $values = [];
+    // Fill out known key values that are shared across entity types.
+    foreach ($keys as $key) {
+      if ($key === 'id' || $key === 'label') {
+        $values[$key] = $this->randomMachineName();
+      }
+    }
+    // Add extra values for particular entity types.
+    switch ($entity_type_id) {
+      case 'block':
+        $values['plugin'] = 'system_powered_by_block';
+        break;
+    }
+    return $values;
+  }
+
 }
diff --git a/core/modules/rest/src/Tests/ReadTest.php b/core/modules/rest/src/Tests/ReadTest.php
index 31b5db8..fb455d2 100644
--- a/core/modules/rest/src/Tests/ReadTest.php
+++ b/core/modules/rest/src/Tests/ReadTest.php
@@ -3,6 +3,8 @@
 namespace Drupal\rest\Tests;
 
 use Drupal\Component\Serialization\Json;
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Url;
 
 /**
@@ -17,7 +19,14 @@ class ReadTest extends RESTTestBase {
    *
    * @var array
    */
-  public static $modules = array('hal', 'rest', 'entity_test');
+  public static $modules = [
+    'hal',
+    'rest',
+    'entity_test',
+    'config_test',
+    'taxonomy',
+    'block',
+  ];
 
   /**
    * Tests several valid and invalid read requests on all entity types.
@@ -25,7 +34,14 @@ class ReadTest extends RESTTestBase {
   public function testRead() {
     // @todo Expand this at least to users.
     // Define the entity types we want to test.
-    $entity_types = array('entity_test', 'node');
+    $entity_types = [
+      'entity_test',
+      'node',
+      'config_test',
+      'taxonomy_vocabulary',
+      'block',
+      'user_role',
+    ];
     foreach ($entity_types as $entity_type) {
       $this->enableService('entity:' . $entity_type, 'GET');
       // Create a user account that has the required permissions to read
@@ -39,23 +55,39 @@ public function testRead() {
       $entity = $this->entityCreate($entity_type);
       $entity->save();
       // Read it over the REST API.
-      $response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
+      $response = $this->httpRequest($this->getReadUrl($entity), 'GET');
       $this->assertResponse('200', 'HTTP response code is correct.');
       $this->assertHeader('content-type', $this->defaultMimeType);
       $data = Json::decode($response);
       // Only assert one example property here, other properties should be
       // checked in serialization tests.
-      $this->assertEqual($data['uuid'][0]['value'], $entity->uuid(), 'Entity UUID is correct');
+      if ($entity instanceof ConfigEntityInterface) {
+        $this->assertEqual($data['uuid'], $entity->uuid(), 'Entity UUID is correct');
+      }
+      else {
+        $this->assertEqual($data['uuid'][0]['value'], $entity->uuid(), 'Entity UUID is correct');
+      }
 
       // Try to read the entity with an unsupported mime format.
-      $response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', 'wrongformat'), 'GET');
+      $this->httpRequest($this->getReadUrl($entity, 'wrongformat'), 'GET');
       $this->assertResponse(406);
       $this->assertHeader('Content-type', 'application/json');
 
       // Try to read an entity that does not exist.
-      $response = $this->httpRequest(Url::fromUri('base://' . $entity_type . '/9999', ['query' => ['_format' => $this->defaultFormat]]), 'GET');
+      $response = $this->httpRequest($this->getReadUrl($entity, $this->defaultFormat, 9999), 'GET');
       $this->assertResponse(404);
-      $path = $entity_type == 'node' ? '/node/{node}' : '/entity_test/{entity_test}';
+      switch ($entity_type) {
+        case 'node':
+          $path = '/node/{node}';
+          break;
+
+        case 'entity_test':
+          $path = '/entity_test/{entity_test}';
+          break;
+
+        default:
+          $path = "/entity/$entity_type/{" . $entity_type . '}';
+      }
       $expected_message = Json::encode(['message' => 'The "' . $entity_type . '" parameter was not converted for the path "' . $path . '" (route name: "rest.entity.' . $entity_type . '.GET.hal_json")']);
       $this->assertIdentical($expected_message, $response, 'Response message is correct.');
 
@@ -65,7 +97,7 @@ public function testRead() {
       if ($entity_type == 'entity_test') {
         $entity->field_test_text->value = 'no access value';
         $entity->save();
-        $response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
+        $response = $this->httpRequest($this->getReadUrl($entity), 'GET');
         $this->assertResponse(200);
         $this->assertHeader('content-type', $this->defaultMimeType);
         $data = Json::decode($response);
@@ -74,14 +106,15 @@ public function testRead() {
 
       // Try to read an entity without proper permissions.
       $this->drupalLogout();
-      $response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
+      $response = $this->httpRequest($this->getReadUrl($entity), 'GET');
       $this->assertResponse(403);
       $this->assertIdentical('{"message":""}', $response);
     }
-    // Try to read a resource which is not REST API enabled.
+    // Try to read a resource, the user entity, which is not REST API enabled.
     $account = $this->drupalCreateUser();
     $this->drupalLogin($account);
-    $response = $this->httpRequest($account->urlInfo()->setRouteParameter('_format', $this->defaultFormat), 'GET');
+    $response = $this->httpRequest($this->getReadUrl($account), 'GET');
+
     // \Drupal\Core\Routing\RequestFormatRouteFilter considers the canonical,
     // non-REST route a match, but a lower quality one: no format restrictions
     // means there's always a match and hence when there is no matching REST
@@ -111,8 +144,50 @@ public function testResourceStructure() {
     $entity->save();
 
     // Read it over the REST API.
-    $response = $this->httpRequest($entity->urlInfo()->setRouteParameter('_format', 'json'), 'GET');
+    $this->httpRequest($this->getReadUrl($entity, 'json'), 'GET');
     $this->assertResponse('200', 'HTTP response code is correct.');
   }
 
+  /**
+   * Gets the read URL object for the entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to get the URL for.
+   * @param string $format
+   *   The format to request the entity in.
+   * @param string $entity_id
+   *   The entity id to use in the URL, defaults to the entity's ID if know
+   *   given.
+   *
+   * @return \Drupal\Core\Url
+   *   The Url object.
+   */
+  protected function getReadUrl(EntityInterface $entity, $format = NULL, $entity_id = NULL) {
+    if (!$format) {
+      $format = $this->defaultFormat;
+    }
+    if (!$entity_id) {
+      $entity_id = $entity->id();
+    }
+    $entity_type = $entity->getEntityTypeId();
+    if ($entity->hasLinkTemplate('canonical')) {
+      $url = $entity->toUrl('canonical');
+    }
+    else {
+      $route_name = 'rest.entity.' . $entity_type . ".GET.";
+      // If testing unsupported format don't use the format to construct route
+      // name. This would give a RouteNotFoundException.
+      if ($format == 'wrongformat') {
+        $route_name .= $this->defaultFormat;
+      }
+      else {
+        $route_name .= $format;
+      }
+      $url = Url::fromRoute($route_name);
+    }
+    $url->setRouteParameter($entity_type, $entity_id);
+    $url->setRouteParameter('_format', $format);
+    return $url;
+  }
+
 }
